Переглянути джерело

Merge pull request #1250 from porter-dev/staging

Webhook token fix + controller 404 fix -> production
abelanger5 4 роки тому
батько
коміт
a39fb3337f

+ 0 - 22
.github/workflows/release.yaml

@@ -314,28 +314,6 @@ jobs:
           asset_path: ./release/windows/porter_${{steps.tag_name.outputs.tag}}_Windows_x86_64.zip
           asset_name: porter_${{steps.tag_name.outputs.tag}}_Windows_x86_64.zip
           asset_content_type: application/zip
-      - name: Upload Windows Server Release Asset
-        id: upload-windows-server-release-asset
-        uses: actions/upload-release-asset@v1
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          GITHUB_TAG: ${{ github.ref }}
-        with:
-          upload_url: ${{ steps.create_release.outputs.upload_url }}
-          asset_path: ./release/windows/portersvr_${{steps.tag_name.outputs.tag}}_Windows_x86_64.zip
-          asset_name: portersvr_${{steps.tag_name.outputs.tag}}_Windows_x86_64.zip
-          asset_content_type: application/zip
-      - name: Upload Windows Docker Credential Release Asset
-        id: upload-windows-docker-cred-release-asset
-        uses: actions/upload-release-asset@v1
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          GITHUB_TAG: ${{ github.ref }}
-        with:
-          upload_url: ${{ steps.create_release.outputs.upload_url }}
-          asset_path: ./release/windows/docker-credential-porter_${{steps.tag_name.outputs.tag}}_Windows_x86_64.zip
-          asset_name: docker-credential-porter_${{steps.tag_name.outputs.tag}}_Windows_x86_64.zip
-          asset_content_type: application/zip
       - name: Upload Static Release Asset
         id: upload-static-release-asset
         uses: actions/upload-release-asset@v1

+ 23 - 0
api/client/k8s.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 
 	"github.com/porter-dev/porter/api/types"
+	v1 "k8s.io/api/batch/v1"
 )
 
 // GetK8sNamespaces gets a namespaces list in a k8s cluster
@@ -66,6 +67,28 @@ func (c *Client) GetRelease(
 	return resp, err
 }
 
+func (c *Client) GetJobs(
+	ctx context.Context,
+	projectID, clusterID uint,
+	namespace, name string,
+) ([]v1.Job, error) {
+	respArr := make([]v1.Job, 0)
+
+	resp := &respArr
+
+	err := c.getRequest(
+		fmt.Sprintf(
+			"/projects/%d/clusters/%d/namespaces/%s/releases/%s/0/jobs",
+			projectID, clusterID,
+			namespace, name,
+		),
+		nil,
+		resp,
+	)
+
+	return *resp, err
+}
+
 // GetK8sAllPods gets all pods for a given release
 func (c *Client) GetK8sAllPods(
 	ctx context.Context,

+ 9 - 1
api/server/handlers/release/get_controllers.go

@@ -1,6 +1,7 @@
 package release
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
@@ -54,7 +55,14 @@ func (c *GetControllersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 		controller.Namespace = helmRelease.Namespace
 		rc, _, err := getController(controller, agent)
 
-		if err != nil {
+		if targetErr := kubernetes.IsNotFoundError; errors.Is(err, targetErr) {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("%s/%s of kind %s was not found", controller.Namespace, controller.Name, controller.Kind),
+				http.StatusNotFound,
+			))
+
+			return
+		} else if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}

+ 25 - 4
api/server/handlers/release/upgrade_webhook.go

@@ -15,6 +15,7 @@ import (
 	"github.com/porter-dev/porter/internal/analytics"
 	"github.com/porter-dev/porter/internal/helm"
 	"github.com/porter-dev/porter/internal/integrations/slack"
+	"gorm.io/gorm"
 )
 
 type WebhookHandler struct {
@@ -40,17 +41,32 @@ func (c *WebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	release, err := c.Repo().Release().ReadReleaseByWebhookToken(token)
 
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-			fmt.Errorf("release not found with given webhook"),
-			http.StatusBadRequest,
-		))
+		if err == gorm.ErrRecordNotFound {
+			// throw forbidden error, since we don't want a way to verify if webhooks exist
+			c.HandleAPIError(w, r, apierrors.NewErrForbidden(
+				fmt.Errorf("release not found with given webhook"),
+			))
+
+			return
+		}
 
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	cluster, err := c.Repo().Cluster().ReadCluster(release.ProjectID, release.ClusterID)
 
 	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			// throw forbidden error, since we don't want a way to verify if the cluster and project
+			// still exist for a cluster that's been deleted
+			c.HandleAPIError(w, r, apierrors.NewErrForbidden(
+				fmt.Errorf("cluster %d in project %d not found for upgrade webhook", release.ClusterID, release.ProjectID),
+			))
+
+			return
+		}
+
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
@@ -70,6 +86,11 @@ func (c *WebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	rel, err := helmAgent.GetRelease(release.Name, 0, true)
 
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
 	// repository is set to current repository by default
 	repository := rel.Config["image"].(map[string]interface{})["repository"]
 

+ 4 - 0
api/types/jobs.go

@@ -1,5 +1,9 @@
 package types
 
+import v1 "k8s.io/api/batch/v1"
+
 const (
 	URLParamJobName URLParam = "name"
 )
+
+type GetJobsResponse []v1.Job

+ 137 - 2
cli/cmd/job.go

@@ -4,15 +4,22 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"strconv"
+	"time"
 
 	"github.com/fatih/color"
 	api "github.com/porter-dev/porter/api/client"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/spf13/cobra"
+	v1 "k8s.io/api/batch/v1"
 )
 
+var jobCmd = &cobra.Command{
+	Use: "job",
+}
+
 var batchImageUpdateCmd = &cobra.Command{
-	Use:   "job update-images",
+	Use:   "update-images",
 	Short: "Updates the image tag of all jobs in a namespace which use a specific image.",
 	Long: fmt.Sprintf(`
 %s 
@@ -43,10 +50,43 @@ use the --namespace flag:
 	},
 }
 
+var waitCmd = &cobra.Command{
+	Use:   "wait",
+	Short: "Waits for a job to complete.",
+	Long: fmt.Sprintf(`
+%s 
+
+Waits for a job with a given name and namespace to complete a run. If the job completes successfully,
+this command exits with exit code 0. Otherwise, this command exits with exit code 1. 
+
+Example commands:
+
+  %s
+
+This command is namespace-scoped and uses the default namespace. To specify a different namespace, 
+use the --namespace flag:
+
+  %s
+`,
+		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job wait\":"),
+		color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example"),
+		color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example --namespace custom-namespace"),
+	),
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, waitForJob)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
 var imageRepoURI string
 
 func init() {
-	rootCmd.AddCommand(batchImageUpdateCmd)
+	rootCmd.AddCommand(jobCmd)
+	jobCmd.AddCommand(batchImageUpdateCmd)
+	jobCmd.AddCommand(waitCmd)
 
 	batchImageUpdateCmd.PersistentFlags().StringVar(
 		&tag,
@@ -72,6 +112,22 @@ func init() {
 
 	batchImageUpdateCmd.MarkPersistentFlagRequired("image-repo-uri")
 	batchImageUpdateCmd.MarkPersistentFlagRequired("tag")
+
+	waitCmd.PersistentFlags().StringVar(
+		&namespace,
+		"namespace",
+		"",
+		"The namespace of the jobs.",
+	)
+
+	waitCmd.PersistentFlags().StringVar(
+		&name,
+		"name",
+		"",
+		"The name of the jobs.",
+	)
+
+	waitCmd.MarkPersistentFlagRequired("name")
 }
 
 func batchImageUpdate(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
@@ -88,3 +144,82 @@ func batchImageUpdate(_ *types.GetAuthenticatedUserResponse, client *api.Client,
 		},
 	)
 }
+
+// waits for a job with a given name/namespace
+func waitForJob(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
+	// get the job release
+	jobRelease, err := client.GetRelease(context.Background(), config.Project, config.Cluster, namespace, name)
+
+	if err != nil {
+		return err
+	}
+
+	// make sure the job chart has a manual job running
+	pausedVal, ok := jobRelease.Release.Config["paused"]
+	pausedErr := fmt.Errorf("this job template is not currently running a manual job")
+
+	if !ok {
+		return pausedErr
+	}
+
+	if pausedValBool, ok := pausedVal.(bool); ok && pausedValBool {
+		return pausedErr
+	}
+
+	// if no job exists with the given revision, wait up to 5 minutes
+	timeWait := time.Now().Add(5 * time.Minute)
+
+	for timeNow := time.Now(); timeNow.Before(timeWait); {
+		// get the jobs for that job chart
+		jobs, err := client.GetJobs(context.Background(), config.Project, config.Cluster, namespace, name)
+
+		if err != nil {
+			return err
+		}
+
+		job := getJobMatchingRevision(uint(jobRelease.Release.Version), jobs)
+
+		if job == nil {
+			time.Sleep(10 * time.Second)
+			continue
+		}
+
+		// once job is running, wait for status to be completed, or failed
+		// if failed, exit with non-zero exit code
+		if job.Status.Failed > 0 {
+			return fmt.Errorf("job failed")
+		}
+
+		if job.Status.Succeeded > 0 {
+			return nil
+		}
+
+		// otherwise, return no error
+		time.Sleep(10 * time.Second)
+		continue
+	}
+
+	return fmt.Errorf("timed out waiting for job")
+}
+
+func getJobMatchingRevision(revision uint, jobs []v1.Job) *v1.Job {
+	for _, job := range jobs {
+		revisionLabel, revisionLabelExists := job.Labels["helm.sh/revision"]
+
+		if !revisionLabelExists {
+			continue
+		}
+
+		jobRevision, err := strconv.ParseUint(revisionLabel, 10, 64)
+
+		if err != nil {
+			continue
+		}
+
+		if uint(jobRevision) == revision {
+			return &job
+		}
+	}
+
+	return nil
+}

+ 20 - 6
internal/kubernetes/agent.go

@@ -357,6 +357,8 @@ func (a *Agent) GetIngress(namespace string, name string) (*v1beta1.Ingress, err
 	)
 }
 
+var IsNotFoundError = fmt.Errorf("not found")
+
 // GetDeployment gets the deployment given the name and namespace
 func (a *Agent) GetDeployment(c grapher.Object) (*appsv1.Deployment, error) {
 	res, err := a.Clientset.AppsV1().Deployments(c.Namespace).Get(
@@ -365,7 +367,9 @@ func (a *Agent) GetDeployment(c grapher.Object) (*appsv1.Deployment, error) {
 		metav1.GetOptions{},
 	)
 
-	if err != nil {
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
 		return nil, err
 	}
 
@@ -382,7 +386,9 @@ func (a *Agent) GetStatefulSet(c grapher.Object) (*appsv1.StatefulSet, error) {
 		metav1.GetOptions{},
 	)
 
-	if err != nil {
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
 		return nil, err
 	}
 
@@ -399,7 +405,9 @@ func (a *Agent) GetReplicaSet(c grapher.Object) (*appsv1.ReplicaSet, error) {
 		metav1.GetOptions{},
 	)
 
-	if err != nil {
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
 		return nil, err
 	}
 
@@ -416,7 +424,9 @@ func (a *Agent) GetDaemonSet(c grapher.Object) (*appsv1.DaemonSet, error) {
 		metav1.GetOptions{},
 	)
 
-	if err != nil {
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
 		return nil, err
 	}
 
@@ -433,7 +443,9 @@ func (a *Agent) GetJob(c grapher.Object) (*batchv1.Job, error) {
 		metav1.GetOptions{},
 	)
 
-	if err != nil {
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
 		return nil, err
 	}
 
@@ -450,7 +462,9 @@ func (a *Agent) GetCronJob(c grapher.Object) (*batchv1beta1.CronJob, error) {
 		metav1.GetOptions{},
 	)
 
-	if err != nil {
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	} else if err != nil {
 		return nil, err
 	}
 

+ 2 - 7
scripts/build/win.sh

@@ -2,12 +2,7 @@
 #
 # Accepts the version as an argument
 
-go build -ldflags="-w -s -X 'github.com/porter-dev/porter/cli/cmd.Version=$1'" -a -tags cli -o ./porter.exe ./cli &
-go build -ldflags="-w -s -X 'main.Version=$1'" -a -o ./docker-credential-porter.exe ./cmd/docker-credential-porter/ &
-go build -ldflags="-w -s -X 'main.Version=$1'" -a -o ./portersvr.exe ./cmd/app/ &
-wait
+go build -ldflags="-w -s -X 'github.com/porter-dev/porter/cli/cmd.Version=$1'" -a -tags cli -o ./porter.exe ./cli
 
 mkdir -p /release/windows
-zip --junk-paths /release/windows/porter_$1_Windows_x86_64.zip ./porter.exe
-zip --junk-paths /release/windows/portersvr_$1_Windows_x86_64.zip ./portersvr.exe
-zip --junk-paths /release/windows/docker-credential-porter_$1_Windows_x86_64.zip ./docker-credential-porter.exe
+zip --junk-paths /release/windows/porter_$1_Windows_x86_64.zip ./porter.exe