mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-24 11:48:04 +00:00
* Object model with self-sufficient binary (#131) * Code cosmetics * Experimenting with object model and direct HELM usage * Experiment with object model * replacing the kubectl * Progressing * Save the progress * Able to start with migration in mind * Migrated two pieces * List releases via Helm * Forgotten field * Cristallized the problem of ctx switcher * Reworked to multi-context * Rollback is also new style * More migration * Refactoring * Describe via code * Bye-bye kubectl binary * Eliminate more old code * Refactor a bit * Merges * No binaries in dockerfile * Commit * Progress with getting the data * Learned the thing about get * One field less * Sstart with repos * Repo add * repo remove * Repos! Icons! * Simplified access to data * Ver listing works * Ver check works * Caching and values * fixup * Done with repos * Working on install * Install work-ish * Fix UI failing on install * Upgrade flow works * Fix image building * Remove outdated test file * Move files around * REfactorings * Cosmetics * Test for cache control (#151) * Files import formatted * Added go-test tools * Added test for no-cache header * added changes * test for cache behaviour of app * test for static route (#153) * Tests: route configuration & context setter (#154) * Test for route configuration * Test for context setter middleware * implemented changes * Restore coverage profile Fixes #156 * Cosmetics * Test for `NewRouter` function (#157) * Test for `configureScanners` (#158) * Test for `configureKubectls` (#163) * Test for repository loading (#169) - Created `repos_test.go` - Test: `Load()` of Repositories * Build all PRs * Fixes failing test (#171) * Fixes failing test - Fixes failing test of repo loading * handles error for * Did some changes * Test for listing of repos (#173) - and did some code formatting Signed-off-by: OmAxiani0 <aximaniom@gmail.com> Signed-off-by: OmAxiani0 <aximaniom@gmail.com> * Test for adding repo (#175) - Modified the `repositories.yml` file Signed-off-by: OmAxiani0 <aximaniom@gmail.com> Signed-off-by: OmAxiani0 <aximaniom@gmail.com> * Test for deleting the repository (#176) * Test for deleting the repository - Also added cleanup function for `TestAdd` * Fixes failing test * Add auto labeler for PR's (#174) * Add auto labeler for PR's * Add all file under .github/workflow to 'ci' label Co-authored-by: Harshit Mehta <harshitm@nvidia.com> * Test for getting repository (#177) * Add github workflow for auto PR labeling (#181) Co-authored-by: Harshit Mehta <harshitm@nvidia.com> * Stub compilation * Fixes around installing * More complex test * Using object model to execute helm test (#191) * Expand test * More test * Coverage * Add mutex for operations * Rectore cluster detection code * Change receiver to pointer * Support multiple namespaces * Cosmetics * Update repos periodically * fix tests * Fix error display * Allow reconfiguring chart without repo * mute linter * Cosmetics * Failing approach to parse manifests Relates to #30 * Report the error properly * ✅ Add test for dashboard/objects/data.go NewDataLayer (#199) * Fix problem of wrong namespace * Added unit tests for releases (#204) * Rework API routes (#197) * Bootstrap OpenAPI doc * Renaming some routes * Listing namespaces * k8s part of things * Repositories section * Document scanners API * One more API call * Progress * Reworked install flow * History endpoint * Textual info section * Resources endpoint * Rollback endpoint * Rollback endpoint * Unit tests * Cleanup * Forgotten tags * Fix tests * TODOs * Rework manifest scanning * add hasTests flag * Adding more information on UI for helm test API response (#195) * Hide test button when no tests Fixes #115 Improves #195 --------- Signed-off-by: OmAxiani0 <aximaniom@gmail.com> Co-authored-by: Om Aximani <75031769+OmAximani0@users.noreply.github.com> Co-authored-by: Harshit Mehta <hdm23061993@gmail.com> Co-authored-by: Harshit Mehta <harshitm@nvidia.com> Co-authored-by: Todd Turner <todd@toddtee.sh> Co-authored-by: arvindsundararajan98 <109727359+arvindsundararajan98@users.noreply.github.com>
631 lines
15 KiB
Go
631 lines
15 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/hexops/gotextdiff"
|
|
"github.com/hexops/gotextdiff/myers"
|
|
"github.com/hexops/gotextdiff/span"
|
|
"github.com/joomcode/errorx"
|
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/objects"
|
|
"github.com/rogpeppe/go-internal/semver"
|
|
log "github.com/sirupsen/logrus"
|
|
"gopkg.in/yaml.v3"
|
|
"helm.sh/helm/v3/pkg/chartutil"
|
|
"helm.sh/helm/v3/pkg/release"
|
|
"helm.sh/helm/v3/pkg/repo"
|
|
helmtime "helm.sh/helm/v3/pkg/time"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
|
)
|
|
|
|
type HelmHandler struct {
|
|
*Contexted
|
|
}
|
|
|
|
func (h *HelmHandler) getRelease(c *gin.Context) *objects.Release {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return nil
|
|
}
|
|
|
|
rel, err := app.Releases.ByName(c.Param("ns"), c.Param("name"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusBadRequest, err)
|
|
return nil
|
|
}
|
|
return rel
|
|
}
|
|
|
|
func (h *HelmHandler) GetReleases(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
rels, err := app.Releases.List()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
res := []*ReleaseElement{}
|
|
for _, r := range rels {
|
|
res = append(res, HReleaseToJSON(r.Orig))
|
|
}
|
|
|
|
c.IndentedJSON(http.StatusOK, res)
|
|
}
|
|
|
|
func (h *HelmHandler) Uninstall(c *gin.Context) {
|
|
rel := h.getRelease(c)
|
|
if rel == nil {
|
|
return // error state is set inside
|
|
}
|
|
|
|
err := rel.Uninstall()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusAccepted)
|
|
}
|
|
|
|
func (h *HelmHandler) Rollback(c *gin.Context) {
|
|
rel := h.getRelease(c)
|
|
if rel == nil {
|
|
return // error state is set inside
|
|
}
|
|
|
|
revn, err := strconv.Atoi(c.PostForm("revision"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
err = rel.Rollback(revn)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusAccepted)
|
|
}
|
|
|
|
func (h *HelmHandler) History(c *gin.Context) {
|
|
rel := h.getRelease(c)
|
|
if rel == nil {
|
|
return // error state is set inside
|
|
}
|
|
|
|
revs, err := rel.History()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
res := []*HistoryElement{}
|
|
for _, r := range revs {
|
|
res = append(res, HReleaseToHistElem(r.Orig))
|
|
}
|
|
|
|
sort.Slice(res, func(i, j int) bool {
|
|
return res[i].Revision < res[j].Revision
|
|
})
|
|
|
|
c.IndentedJSON(http.StatusOK, res)
|
|
}
|
|
|
|
func (h *HelmHandler) Resources(c *gin.Context) {
|
|
h.EnableClientCache(c)
|
|
|
|
rel := h.getRelease(c)
|
|
if rel == nil {
|
|
return // error state is set inside
|
|
}
|
|
|
|
res, err := objects.ParseManifests(rel.Orig.Manifest)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.IndentedJSON(http.StatusOK, res)
|
|
}
|
|
|
|
func (h *HelmHandler) RepoVersions(c *gin.Context) {
|
|
qp, err := utils.GetQueryProps(c)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
repos, err := app.Repositories.Containing(qp.Name)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
res := []*RepoChartElement{}
|
|
for _, r := range repos {
|
|
res = append(res, &RepoChartElement{
|
|
Name: r.Name,
|
|
Version: r.Version,
|
|
AppVersion: r.AppVersion,
|
|
Description: r.Description,
|
|
Repository: r.Annotations[objects.AnnRepo],
|
|
})
|
|
}
|
|
|
|
c.IndentedJSON(http.StatusOK, res)
|
|
}
|
|
|
|
func (h *HelmHandler) RepoLatestVer(c *gin.Context) {
|
|
qp, err := utils.GetQueryProps(c)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
rep, err := app.Repositories.Containing(qp.Name)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
res := []*RepoChartElement{}
|
|
for _, r := range rep {
|
|
res = append(res, &RepoChartElement{
|
|
Name: r.Name,
|
|
Version: r.Version,
|
|
AppVersion: r.AppVersion,
|
|
Description: r.Description,
|
|
Repository: r.Annotations[objects.AnnRepo],
|
|
})
|
|
}
|
|
|
|
sort.Slice(res, func(i, j int) bool {
|
|
return semver.Compare(res[i].Version, res[j].Version) > 0
|
|
})
|
|
|
|
if len(res) > 0 {
|
|
c.IndentedJSON(http.StatusOK, res[:1])
|
|
} else {
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
func (h *HelmHandler) RepoCharts(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
rep, err := app.Repositories.Get(c.Param("name"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
charts, err := rep.Charts()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
installed, err := app.Releases.List()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
// TODO: enrich with installed
|
|
enrichRepoChartsWithInstalled(charts, installed)
|
|
|
|
sort.Slice(charts, func(i, j int) bool {
|
|
return charts[i].Name < charts[j].Name
|
|
})
|
|
|
|
c.IndentedJSON(http.StatusOK, charts)
|
|
}
|
|
|
|
func enrichRepoChartsWithInstalled(charts []*repo.ChartVersion, installed []*objects.Release) {
|
|
for _, rchart := range charts {
|
|
for _, rel := range installed {
|
|
if rchart.Metadata.Name == rel.Orig.Chart.Name() {
|
|
log.Debugf("Matched") // TODO: restore implementation
|
|
// TODO: there can be more than one
|
|
//rchart.InstalledNamespace = rel.Orig.Namespace
|
|
//rchart.InstalledName = rel.Orig.Name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *HelmHandler) RepoUpdate(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
rep, err := app.Repositories.Get(c.Param("name"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
err = rep.Update()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *HelmHandler) Install(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
values := map[string]interface{}{}
|
|
err := yaml.Unmarshal([]byte(c.PostForm("values")), &values)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
justTemplate := c.PostForm("preview") == "true"
|
|
ns := c.Param("ns")
|
|
if ns == "[empty]" {
|
|
ns = ""
|
|
}
|
|
rel, err := app.Releases.Install(ns, c.PostForm("name"), c.PostForm("chart"), c.PostForm("version"), justTemplate, values)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
if justTemplate {
|
|
c.IndentedJSON(http.StatusOK, rel)
|
|
} else {
|
|
c.IndentedJSON(http.StatusAccepted, rel)
|
|
}
|
|
}
|
|
|
|
func (h *HelmHandler) Upgrade(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
existing, err := app.Releases.ByName(c.Param("ns"), c.Param("name"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
values := map[string]interface{}{}
|
|
err = yaml.Unmarshal([]byte(c.PostForm("values")), &values)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
justTemplate := c.PostForm("preview") == "true"
|
|
rel, err := existing.Upgrade(c.PostForm("chart"), c.PostForm("version"), justTemplate, values)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
if justTemplate {
|
|
c.IndentedJSON(http.StatusOK, rel)
|
|
} else {
|
|
c.IndentedJSON(http.StatusAccepted, rel)
|
|
}
|
|
}
|
|
|
|
func (h *HelmHandler) RunTests(c *gin.Context) {
|
|
rel := h.getRelease(c)
|
|
if rel == nil {
|
|
return // error state is set inside
|
|
}
|
|
|
|
out, err := rel.RunTests()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
c.String(http.StatusOK, out)
|
|
}
|
|
|
|
func (h *HelmHandler) GetInfoSection(c *gin.Context) {
|
|
if c.Query("revision") != "" { // don't cache if latest is requested
|
|
h.EnableClientCache(c)
|
|
}
|
|
|
|
rel := h.getRelease(c)
|
|
if rel == nil {
|
|
return // error state is set inside
|
|
}
|
|
|
|
revn, err := strconv.Atoi(c.Query("revision"))
|
|
if c.Query("revision") != "" && err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
rev, err := rel.GetRev(revn)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
var revDiff *objects.Release
|
|
revS := c.Query("revisionDiff")
|
|
if revS != "" {
|
|
revN, err := strconv.Atoi(revS)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
revDiff, err = rel.GetRev(revN)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
flag := c.Query("userDefined") == "true"
|
|
|
|
res, err := h.handleGetSection(rev, c.Param("section"), revDiff, flag)
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
c.String(http.StatusOK, res)
|
|
}
|
|
|
|
func (h *HelmHandler) RepoValues(c *gin.Context) {
|
|
h.EnableClientCache(c)
|
|
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
out, err := app.Repositories.GetChartValues(c.Query("chart"), c.Query("version"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.String(http.StatusOK, out)
|
|
}
|
|
|
|
func (h *HelmHandler) RepoList(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
repos, err := app.Repositories.List()
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
out := []RepositoryElement{}
|
|
for _, r := range repos {
|
|
out = append(out, RepositoryElement{
|
|
Name: r.Orig.Name,
|
|
URL: r.Orig.URL,
|
|
})
|
|
}
|
|
|
|
c.IndentedJSON(http.StatusOK, out)
|
|
}
|
|
|
|
func (h *HelmHandler) RepoAdd(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
// TODO: more repo options to accept
|
|
err := app.Repositories.Add(c.PostForm("name"), c.PostForm("url"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *HelmHandler) RepoDelete(c *gin.Context) {
|
|
app := h.GetApp(c)
|
|
if app == nil {
|
|
return // sets error inside
|
|
}
|
|
|
|
err := app.Repositories.Delete(c.Param("name"))
|
|
if err != nil {
|
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *HelmHandler) handleGetSection(rel *objects.Release, section string, rDiff *objects.Release, flag bool) (string, error) {
|
|
sections := map[string]objects.SectionFn{
|
|
"manifests": func(qp *release.Release, b bool) (string, error) { return qp.Manifest, nil },
|
|
"notes": func(qp *release.Release, b bool) (string, error) { return qp.Info.Notes, nil },
|
|
"values": func(qp *release.Release, b bool) (string, error) {
|
|
allVals := qp.Config
|
|
|
|
if !b {
|
|
merged, err := chartutil.CoalesceValues(qp.Chart, qp.Config)
|
|
if err != nil {
|
|
return "", errorx.Decorate(err, "failed to merge chart vals with user defined")
|
|
}
|
|
allVals = merged
|
|
}
|
|
|
|
if len(allVals) > 0 {
|
|
data, err := yaml.Marshal(allVals)
|
|
if err != nil {
|
|
return "", errorx.Decorate(err, "failed to serialize values into YAML")
|
|
}
|
|
return string(data), nil
|
|
}
|
|
return "", nil
|
|
},
|
|
}
|
|
|
|
functor, found := sections[section]
|
|
if !found {
|
|
return "", errors.New("unsupported section: " + section)
|
|
}
|
|
|
|
if rDiff != nil {
|
|
ext := ".yaml"
|
|
if section == "notes" {
|
|
ext = ".txt"
|
|
}
|
|
|
|
res, err := RevisionDiff(functor, ext, rDiff.Orig, rel.Orig, flag)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
res, err := functor(rel.Orig, flag)
|
|
if err != nil {
|
|
return "", errorx.Decorate(err, "failed to get section info")
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
type RepoChartElement struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
AppVersion string `json:"app_version"`
|
|
Description string `json:"description"`
|
|
|
|
InstalledNamespace string `json:"installed_namespace"` // custom addition on top of Helm
|
|
InstalledName string `json:"installed_name"` // custom addition on top of Helm
|
|
Repository string `json:"repository"`
|
|
}
|
|
|
|
func HReleaseToJSON(o *release.Release) *ReleaseElement {
|
|
return &ReleaseElement{
|
|
Name: o.Name,
|
|
Namespace: o.Namespace,
|
|
Revision: strconv.Itoa(o.Version),
|
|
Updated: o.Info.LastDeployed,
|
|
Status: o.Info.Status,
|
|
Chart: fmt.Sprintf("%s-%s", o.Chart.Name(), o.Chart.Metadata.Version),
|
|
AppVersion: o.Chart.AppVersion(),
|
|
Icon: o.Chart.Metadata.Icon,
|
|
Description: o.Chart.Metadata.Description,
|
|
}
|
|
}
|
|
|
|
type ReleaseElement struct {
|
|
Name string `json:"name"`
|
|
Namespace string `json:"namespace"`
|
|
Revision string `json:"revision"`
|
|
Updated helmtime.Time `json:"updated"`
|
|
Status release.Status `json:"status"`
|
|
Chart string `json:"chart"`
|
|
AppVersion string `json:"app_version"`
|
|
Icon string `json:"icon"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type RepositoryElement struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type HistoryElement struct {
|
|
Revision int `json:"revision"`
|
|
Updated helmtime.Time `json:"updated"`
|
|
Status release.Status `json:"status"`
|
|
Chart string `json:"chart"`
|
|
AppVersion string `json:"app_version"`
|
|
Description string `json:"description"`
|
|
|
|
ChartName string `json:"chart_name"` // custom addition on top of Helm
|
|
ChartVer string `json:"chart_ver"` // custom addition on top of Helm
|
|
HasTests bool `json:"has_tests"`
|
|
}
|
|
|
|
func HReleaseToHistElem(o *release.Release) *HistoryElement {
|
|
return &HistoryElement{
|
|
Revision: o.Version,
|
|
Updated: o.Info.LastDeployed,
|
|
Status: o.Info.Status,
|
|
Chart: fmt.Sprintf("%s-%s", o.Chart.Name(), o.Chart.Metadata.Version),
|
|
AppVersion: o.Chart.AppVersion(),
|
|
Description: o.Info.Description,
|
|
ChartName: o.Chart.Name(),
|
|
ChartVer: o.Chart.Metadata.Version,
|
|
HasTests: releaseHasTests(o),
|
|
}
|
|
}
|
|
|
|
func RevisionDiff(functor objects.SectionFn, ext string, revision1 *release.Release, revision2 *release.Release, flag bool) (string, error) {
|
|
if revision1 == nil || revision2 == nil {
|
|
log.Debugf("One of revisions is nil: %v %v", revision1, revision2)
|
|
return "", nil
|
|
}
|
|
|
|
manifest1, err := functor(revision1, flag)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
manifest2, err := functor(revision2, flag)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
diff := GetDiff(manifest1, manifest2, strconv.Itoa(revision1.Version)+ext, strconv.Itoa(revision2.Version)+ext)
|
|
return diff, nil
|
|
}
|
|
|
|
func GetDiff(text1 string, text2 string, name1 string, name2 string) string {
|
|
edits := myers.ComputeEdits(span.URIFromPath(""), text1, text2)
|
|
unified := gotextdiff.ToUnified(name1, name2, text1, edits)
|
|
diff := fmt.Sprint(unified)
|
|
log.Debugf("The diff is: %s", diff)
|
|
return diff
|
|
}
|
|
|
|
func releaseHasTests(o *release.Release) bool {
|
|
for _, h := range o.Hooks {
|
|
for _, e := range h.Events {
|
|
if e == release.HookTest {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|