Browse Source

add batch update command

Alexander Belanger 5 years ago
parent
commit
dd10b0d5ad
5 changed files with 228 additions and 0 deletions
  1. 42 0
      cli/cmd/api/deploy.go
  2. 59 0
      cli/cmd/job.go
  3. 7 0
      internal/forms/release.go
  4. 106 0
      server/api/release_handler.go
  5. 14 0
      server/router/router.go

+ 42 - 0
cli/cmd/api/deploy.go

@@ -2,9 +2,11 @@ package api
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"net/url"
+	"strings"
 
 	"github.com/porter-dev/porter/internal/models"
 )
@@ -72,3 +74,43 @@ func (c *Client) DeployWithWebhook(
 
 	return nil
 }
+
+type UpdateBatchImageRequest struct {
+	ImageRepoURI string `json:"image_repo_uri"`
+	Tag          string `json:"tag"`
+}
+
+// UpdateBatchImage updates all releases that use a certain image with a new tag
+func (c *Client) UpdateBatchImage(
+	ctx context.Context,
+	projID, clusterID uint,
+	updateImageReq *UpdateBatchImageRequest,
+) error {
+	data, err := json.Marshal(updateImageReq)
+
+	if err != nil {
+		return nil
+	}
+
+	req, err := http.NewRequest(
+		"POST",
+		fmt.Sprintf("%s/projects/%d/releases/image/update/batch?cluster_id=%d", c.BaseURL, projID, clusterID),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req = req.WithContext(ctx)
+
+	if httpErr, err := c.sendRequest(req, nil, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return err
+	}
+
+	return nil
+}

+ 59 - 0
cli/cmd/job.go

@@ -0,0 +1,59 @@
+package cmd
+
+import (
+	"context"
+	"os"
+
+	"github.com/fatih/color"
+	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/spf13/cobra"
+)
+
+var batchImageUpdateCmd = &cobra.Command{
+	Use:   "update-image",
+	Short: "Blerp.",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, batchImageUpdate)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
+var imageRepoURI string
+
+func init() {
+	rootCmd.AddCommand(batchImageUpdateCmd)
+
+	batchImageUpdateCmd.PersistentFlags().StringVar(
+		&tag,
+		"tag",
+		"",
+		"Tag",
+	)
+
+	batchImageUpdateCmd.MarkPersistentFlagRequired("app")
+
+	batchImageUpdateCmd.PersistentFlags().StringVarP(
+		&imageRepoURI,
+		"image-repo-uri",
+		"i",
+		"",
+		"Image repo uri",
+	)
+}
+
+func batchImageUpdate(resp *api.AuthCheckResponse, client *api.Client, args []string) error {
+	color.New(color.FgGreen).Println("Update releases with image:", imageRepoURI)
+
+	return client.UpdateBatchImage(
+		context.TODO(),
+		config.Project,
+		config.Cluster,
+		&api.UpdateBatchImageRequest{
+			ImageRepoURI: imageRepoURI,
+			Tag:          tag,
+		},
+	)
+}

+ 7 - 0
internal/forms/release.go

@@ -130,3 +130,10 @@ type InstallChartTemplateForm struct {
 	// optional git action config
 	GithubActionConfig *CreateGitActionOptional `json:"github_action,omitempty"`
 }
+
+// UpdateImageForm represents the accepted values for updating a Helm release's image
+type UpdateImageForm struct {
+	*ReleaseForm
+	ImageRepoURI string `json:"image_repo_uri" form:"required"`
+	Tag          string `json:"tag" form:"required"`
+}

+ 106 - 0
server/api/release_handler.go

@@ -7,6 +7,7 @@ import (
 	"net/url"
 	"strconv"
 	"strings"
+	"sync"
 
 	"github.com/porter-dev/porter/internal/kubernetes/prometheus"
 	"github.com/porter-dev/porter/internal/models"
@@ -973,6 +974,111 @@ func (app *App) HandleReleaseDeployWebhook(w http.ResponseWriter, r *http.Reques
 	w.WriteHeader(http.StatusOK)
 }
 
+// HandleReleaseJobUpdateImage
+func (app *App) HandleReleaseBatchUpdateImage(w http.ResponseWriter, r *http.Request) {
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+
+	form := &forms.UpdateImageForm{
+		ReleaseForm: &forms.ReleaseForm{
+			Form: &helm.Form{
+				Repo:              app.Repo,
+				DigitalOceanOAuth: app.DOConf,
+			},
+		},
+	}
+
+	form.ReleaseForm.PopulateHelmOptionsFromQueryParams(
+		vals,
+		app.Repo.Cluster,
+	)
+
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		app.handleErrorFormDecoding(err, ErrUserDecode, w)
+		return
+	}
+
+	releases, err := app.Repo.Release.ListReleasesByImageRepoURI(form.Cluster.ID, form.ImageRepoURI)
+
+	if err != nil {
+		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+			Code:   ErrReleaseReadData,
+			Errors: []string{"releases not found with given image repo uri"},
+		}, w)
+
+		return
+	}
+
+	agent, err := app.getAgentFromReleaseForm(
+		w,
+		r,
+		form.ReleaseForm,
+	)
+
+	// errors are handled in app.getAgentFromBodyParams
+	if err != nil {
+		return
+	}
+
+	registries, err := app.Repo.Registry.ListRegistriesByProjectID(uint(form.ReleaseForm.Cluster.ProjectID))
+
+	if err != nil {
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	// asynchronously update releases with that image repo uri
+	var wg sync.WaitGroup
+	mu := &sync.Mutex{}
+	errors := make([]string, 0)
+
+	for i := range releases {
+		index := i
+		wg.Add(1)
+
+		go func() {
+			defer wg.Done()
+			// read release via agent
+			rel, err := agent.GetRelease(releases[index].Name, 0)
+
+			if err != nil {
+				mu.Lock()
+				errors = append(errors, err.Error())
+				mu.Unlock()
+			}
+
+			image := map[string]interface{}{}
+			image["repository"] = releases[index].ImageRepoURI
+			image["tag"] = form.Tag
+			rel.Config["image"] = image
+
+			conf := &helm.UpgradeReleaseConfig{
+				Name:       releases[index].Name,
+				Cluster:    form.ReleaseForm.Cluster,
+				Repo:       *app.Repo,
+				Registries: registries,
+				Values:     rel.Config,
+			}
+
+			_, err = agent.UpgradeReleaseByValues(conf, app.DOConf)
+
+			if err != nil {
+				mu.Lock()
+				errors = append(errors, err.Error())
+				mu.Unlock()
+			}
+		}()
+	}
+
+	wg.Wait()
+
+	w.WriteHeader(http.StatusOK)
+}
+
 // HandleRollbackRelease rolls a release back to a specified revision
 func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
 	name := chi.URLParam(r, "name")

+ 14 - 0
server/router/router.go

@@ -1426,6 +1426,20 @@ func New(a *api.App) *chi.Mux {
 					mw.ReadAccess,
 				),
 			)
+
+			r.Method(
+				"POST",
+				"/projects/{project_id}/releases/image/update/batch",
+				auth.DoesUserHaveProjectAccess(
+					auth.DoesUserHaveClusterAccess(
+						requestlog.NewHandler(a.HandleReleaseBatchUpdateImage, l),
+						mw.URLParam,
+						mw.QueryParam,
+					),
+					mw.URLParam,
+					mw.WriteAccess,
+				),
+			)
 		})
 	})