github.com/section/sectionctl@v1.12.3/commands/gitService.go (about)

     1  package commands
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/alecthomas/kong"
    12  	"github.com/go-git/go-git/v5"
    13  	"github.com/go-git/go-git/v5/plumbing"
    14  	"github.com/go-git/go-git/v5/plumbing/object"
    15  	gitHTTP "github.com/go-git/go-git/v5/plumbing/transport/http"
    16  	"github.com/rs/zerolog/log"
    17  	"github.com/section/sectionctl/api"
    18  )
    19  
    20  // GitService interface provides a way to interact with Git
    21  type GitService interface {
    22  	UpdateGitViaGit(ctx *kong.Context, c *DeployCmd, response UploadResponse, logWriters *LogWriters) error
    23  }
    24  
    25  // GS ...
    26  type GS struct{}
    27  
    28  // This is far less then ideal, however Kong does not seem to provide a way to inject dependencies into its commands so we must use this for testing
    29  var globalGitService GitService = &GS{}
    30  
    31  // UpdateGitViaGit clones the application repository to a temporary directory then updates it with the latest payload id and pushes a new commit
    32  func (g *GS) UpdateGitViaGit(ctx *kong.Context, c *DeployCmd, response UploadResponse,logWriters *LogWriters) error {
    33  	app, err := api.Application(c.AccountID, c.AppID)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	appName := strings.ReplaceAll(app.ApplicationName, "/", "")
    38  	cloneDir := fmt.Sprintf("https://aperture.section.io/account/%d/application/%d/%s.git", c.AccountID, c.AppID, appName)
    39  	log.Debug().Msg(fmt.Sprintf(" Begin updating hash in .section-external-source.json:\n\tsection-configmap-tars/%v/%s.tar.gz\n",c.AccountID,response.PayloadID))
    40  	tempDir, err := ioutil.TempDir("", "sectionctl-*")
    41  	if err != nil {
    42  		return err
    43  	}
    44  	log.Debug().Msg(fmt.Sprintln("tempDir: ", tempDir))
    45  	// Git objects storer based on memory
    46  	gitAuth := &gitHTTP.BasicAuth{
    47  		Username: "section-token", // yes, this can be anything except an empty string
    48  		Password: api.Token,
    49  	}
    50  	payload := PayloadValue{ID: response.PayloadID}
    51  	branchRef := fmt.Sprintf("refs/heads/%s",c.Environment)
    52  	var r *git.Repository
    53  	progressOutput := logWriters.CarriageReturnWriter
    54  	log.Info().Msg(fmt.Sprintln("Cloning section config repo for your application to ",tempDir))
    55  	r, err = git.PlainClone(tempDir, false, &git.CloneOptions{
    56  		URL:      cloneDir,
    57  		Auth:     gitAuth,
    58  		Progress: progressOutput,
    59  		ReferenceName: plumbing.ReferenceName(branchRef),
    60  	})
    61  	
    62  	if err != nil {
    63  		log.Error().Err(err).Msg("error cloning")
    64  		return err
    65  	}
    66  	// ... retrieving the branch being pointed by HEAD
    67  	ref, err := r.Head()
    68  	if err != nil {
    69  		log.Error().Err(err).Msg("error retrieving the git HEAD")
    70  		return err
    71  	}
    72  	// ... retrieving the commit object
    73  	commit, err := r.CommitObject(ref.Hash())
    74  	if err != nil {
    75  		log.Error().Err(err).Msg("error retrieving the commit hash")
    76  		return err
    77  	}
    78  	log.Debug().Msg(fmt.Sprintln("HEAD commit: ", commit))
    79  	// ... retrieve the tree from the commit
    80  	tree, err := commit.Tree()
    81  	if err != nil {
    82  		return err
    83  	}
    84  	w, err := r.Worktree()
    85  	if err != nil {
    86  		return err
    87  	}
    88  	f, err := tree.File(c.AppPath + "/.section-external-source.json")
    89  	if err != nil {
    90  		return err
    91  	}
    92  	srcContent := PayloadValue{}
    93  	content, err := f.Contents()
    94  	if err != nil {
    95  		return fmt.Errorf("couldn't open contents of file: %w", err)
    96  	}
    97  	err = json.Unmarshal([]byte(content), &srcContent)
    98  	if err != nil {
    99  		return fmt.Errorf("failed to unmarshal json: %w", err)
   100  	}
   101  	log.Debug().Str("Old tarball UUID",  content);
   102  	log.Debug().Str("New tarball UUID",  response.PayloadID)
   103  	srcContent.ID = payload.ID
   104  	pl, err := json.MarshalIndent(srcContent, "", "\t")
   105  	if err != nil {
   106  		return err
   107  	}
   108  	err = ioutil.WriteFile(filepath.Join(tempDir, c.AppPath+"/.section-external-source.json"), pl, 0644)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	_, err = w.Add(c.AppPath + "/.section-external-source.json")
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	status, err := w.Status()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	log.Debug().Msg(fmt.Sprintln("git status: ", status))
   122  	_, err = w.Add(c.AppPath + "/.section-external-source.json")
   123  	if err != nil {
   124  		return err
   125  	}
   126  	commitHash, err := w.Commit("[sectionctl] updated nodejs/.section-external-source.json with new deployment.", &git.CommitOptions{Author: &object.Signature{
   127  		Name:  "sectionctl",
   128  		Email: "noreply@section.io",
   129  		When:  time.Now(),
   130  	}})
   131  	if err != nil {
   132  		return fmt.Errorf("failed to make a commit on the temporary repository: %w", err)
   133  	}
   134  	cmt, err := r.CommitObject(commitHash)
   135  	if err != nil {
   136  		return fmt.Errorf("failed to get commit object: %w", err)
   137  	}
   138  	log.Debug().Msg(fmt.Sprintln("New Commit: ", cmt.String()))
   139  	newTree, err := cmt.Tree()
   140  	if err != nil {
   141  		return err
   142  	}
   143  	newF, err := newTree.File(c.AppPath + "/.section-external-source.json")
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	ctt, err := newF.Contents()
   149  	if err != nil {
   150  		return fmt.Errorf("could not open contents of new file in git: %w", err)
   151  	}
   152  	log.Debug().Msg(fmt.Sprintln("contents in new commit: ", ctt))
   153  	
   154  	configFile, err := tree.File("section.config.json")
   155  	if err != nil {
   156  		log.Error().Err(err).Msg("unable to open section.config.json which is used to log the image name and version")
   157  	}
   158  	sectionConfigContents, err := configFile.Contents()
   159  	if err != nil {
   160  		log.Error().Err(err).Msg("unable to open section.config.json which is used to log the image name and version")
   161  	}
   162  	sectionConfig, err := ParseSectionConfig(sectionConfigContents)
   163  	if err != nil{
   164  		log.Error().Err(err).Msg("There was an issue reading the section.config.json")
   165  	}
   166  	// if err := json.Unmarshal(sectionConfigContent.Bytes(), &sectionConfig); err != nil {
   167  	// 	log.Error().Err(err).Msg("unable to decode the json for section.config.json which is used to log the image name and version")
   168  	// }
   169  	moduleVersion := "unknown"
   170  	for _,v := range sectionConfig.Proxychain{
   171  		if(v.Name == c.AppPath){
   172  			moduleVersion = v.Image
   173  		}
   174  	}
   175  	if moduleVersion == "unknown"{
   176  		log.Debug().Msg("failed to pair app path (aka proxy name) with image (version)")
   177  	}
   178  	// for proxy, _ := range sectionConfig["proxychain"]{
   179  
   180  	// }
   181  	log.Info().Str("Git Remote",cloneDir).Msg("")
   182  	log.Info().Str("Tarball Source",fmt.Sprintf("%v/%s.tar.gz",c.AccountID,response.PayloadID)).Msg("")
   183  	log.Info().Str("Module Name",c.AppPath).Msg("")
   184  	log.Info().Str("Module Version",moduleVersion).Msg("")
   185  	log.Info().Msg("Validating your app...")
   186  	err = r.Push(&git.PushOptions{Auth: gitAuth, Progress: progressOutput})
   187  
   188  	if err != nil {
   189  		return fmt.Errorf("failed to push git changes: %w", err)
   190  	}
   191  	
   192  	return nil
   193  }