|  | @@ -12,12 +12,18 @@ import (
 | 
	
		
			
			| 12 | 12 |  	"github.com/sirupsen/logrus"
 | 
	
		
			
			| 13 | 13 |  )
 | 
	
		
			
			| 14 | 14 |  
 | 
	
		
			
			|  | 15 | +// Setup loads (and synchronise if needed) the Git repository mentioned in the
 | 
	
		
			
			|  | 16 | +// configuration file, then creates the poller that will pull from the Git
 | 
	
		
			
			|  | 17 | +// repository on a regular basis and push all the changes to Grafana.
 | 
	
		
			
			|  | 18 | +// Returns an error if the poller encountered one.
 | 
	
		
			
			| 15 | 19 |  func Setup(cfg *config.Config, client *grafana.Client, delRemoved bool) error {
 | 
	
		
			
			|  | 20 | +	// Load the Git repository.
 | 
	
		
			
			| 16 | 21 |  	r, needsSync, err := git.NewRepository(cfg.Git)
 | 
	
		
			
			| 17 | 22 |  	if err != nil {
 | 
	
		
			
			| 18 | 23 |  		return err
 | 
	
		
			
			| 19 | 24 |  	}
 | 
	
		
			
			| 20 | 25 |  
 | 
	
		
			
			|  | 26 | +	// Synchronise the repository if needed.
 | 
	
		
			
			| 21 | 27 |  	if needsSync {
 | 
	
		
			
			| 22 | 28 |  		if err = r.Sync(false); err != nil {
 | 
	
		
			
			| 23 | 29 |  			return err
 | 
	
	
		
			
			|  | @@ -26,6 +32,8 @@ func Setup(cfg *config.Config, client *grafana.Client, delRemoved bool) error {
 | 
	
		
			
			| 26 | 32 |  
 | 
	
		
			
			| 27 | 33 |  	errs := make(chan error, 1)
 | 
	
		
			
			| 28 | 34 |  
 | 
	
		
			
			|  | 35 | +	// In the future we may want to poll from several Git repositories, so we
 | 
	
		
			
			|  | 36 | +	// run the poller in a go routine.
 | 
	
		
			
			| 29 | 37 |  	go func() {
 | 
	
		
			
			| 30 | 38 |  		if err = poller(cfg, r, client, delRemoved); err != nil {
 | 
	
		
			
			| 31 | 39 |  			errs <- err
 | 
	
	
		
			
			|  | @@ -37,10 +45,23 @@ func Setup(cfg *config.Config, client *grafana.Client, delRemoved bool) error {
 | 
	
		
			
			| 37 | 45 |  	return err
 | 
	
		
			
			| 38 | 46 |  }
 | 
	
		
			
			| 39 | 47 |  
 | 
	
		
			
			|  | 48 | +// poller gets the current status of the Git repository that has previously been
 | 
	
		
			
			|  | 49 | +// loaded, and then starts an infinite loop that will pull from the Git
 | 
	
		
			
			|  | 50 | +// remote, then, if there was any new commit, retrieve the contents of the
 | 
	
		
			
			|  | 51 | +// modified and added files to push them to Grafana. If set by the user via
 | 
	
		
			
			|  | 52 | +// a command-line flag, it will also check for removed files and delete the
 | 
	
		
			
			|  | 53 | +// corresponding dashboards from Grafana. It then sleeps for the time specified
 | 
	
		
			
			|  | 54 | +// in the configuration file, before starting its next iteration.
 | 
	
		
			
			|  | 55 | +// Returns an error if there was an issue checking the Git repository status,
 | 
	
		
			
			|  | 56 | +// synchronising it, reading the files' contents or discussing with the Grafana
 | 
	
		
			
			|  | 57 | +// API
 | 
	
		
			
			| 40 | 58 |  func poller(
 | 
	
		
			
			| 41 | 59 |  	cfg *config.Config, repo *git.Repository, client *grafana.Client,
 | 
	
		
			
			| 42 | 60 |  	delRemoved bool,
 | 
	
		
			
			| 43 | 61 |  ) (err error) {
 | 
	
		
			
			|  | 62 | +	// Get current state of the repo.
 | 
	
		
			
			|  | 63 | +	// This is mainly to give an initial value to variables that will see their
 | 
	
		
			
			|  | 64 | +	// content changed with every iteration of the loop.
 | 
	
		
			
			| 44 | 65 |  	latestCommit, err := repo.GetLatestCommit()
 | 
	
		
			
			| 45 | 66 |  	if err != nil {
 | 
	
		
			
			| 46 | 67 |  		return
 | 
	
	
		
			
			|  | @@ -51,38 +72,58 @@ func poller(
 | 
	
		
			
			| 51 | 72 |  		return
 | 
	
		
			
			| 52 | 73 |  	}
 | 
	
		
			
			| 53 | 74 |  
 | 
	
		
			
			|  | 75 | +	// We'll need to know the previous commit in order to compare its hash with
 | 
	
		
			
			|  | 76 | +	// the one from the most recent commit after we pull from the remote, se we
 | 
	
		
			
			|  | 77 | +	// know if there was any new commit.
 | 
	
		
			
			| 54 | 78 |  	previousCommit := latestCommit
 | 
	
		
			
			|  | 79 | +	// We need to store the content of the files from the previous iteration of
 | 
	
		
			
			|  | 80 | +	// the loop in order to manage removed files which contents won't be
 | 
	
		
			
			|  | 81 | +	// accessible anymore.
 | 
	
		
			
			| 55 | 82 |  	previousFilesContents := filesContents
 | 
	
		
			
			| 56 | 83 |  
 | 
	
		
			
			|  | 84 | +	// Start looping
 | 
	
		
			
			| 57 | 85 |  	for {
 | 
	
		
			
			|  | 86 | +		// Synchronise the repository (i.e. pull from remote).
 | 
	
		
			
			| 58 | 87 |  		if err = repo.Sync(true); err != nil {
 | 
	
		
			
			| 59 | 88 |  			return
 | 
	
		
			
			| 60 | 89 |  		}
 | 
	
		
			
			| 61 | 90 |  
 | 
	
		
			
			|  | 91 | +		// Retrieve the latest commit in order to compare its hash with the
 | 
	
		
			
			|  | 92 | +		// previous one.
 | 
	
		
			
			| 62 | 93 |  		latestCommit, err = repo.GetLatestCommit()
 | 
	
		
			
			| 63 | 94 |  		if err != nil {
 | 
	
		
			
			| 64 | 95 |  			return
 | 
	
		
			
			| 65 | 96 |  		}
 | 
	
		
			
			| 66 | 97 |  
 | 
	
		
			
			|  | 98 | +		// If there is at least one new commit, handle the changes it introduces.
 | 
	
		
			
			| 67 | 99 |  		if previousCommit.Hash.String() != latestCommit.Hash.String() {
 | 
	
		
			
			| 68 | 100 |  			logrus.WithFields(logrus.Fields{
 | 
	
		
			
			| 69 | 101 |  				"previous_hash": previousCommit.Hash.String(),
 | 
	
		
			
			| 70 | 102 |  				"new_hash":      latestCommit.Hash.String(),
 | 
	
		
			
			| 71 | 103 |  			}).Info("New commit(s) detected")
 | 
	
		
			
			| 72 | 104 |  
 | 
	
		
			
			|  | 105 | +			// Get the updated files contents.
 | 
	
		
			
			| 73 | 106 |  			filesContents, err = repo.GetFilesContentsAtCommit(latestCommit)
 | 
	
		
			
			| 74 | 107 |  			if err != nil {
 | 
	
		
			
			| 75 | 108 |  				return err
 | 
	
		
			
			| 76 | 109 |  			}
 | 
	
		
			
			| 77 | 110 |  
 | 
	
		
			
			|  | 111 | +			// Get the name of the files that have been added/modified and
 | 
	
		
			
			|  | 112 | +			// removed between the two iterations.
 | 
	
		
			
			| 78 | 113 |  			modified, removed, err := repo.GetModifiedAndRemovedFiles(previousCommit, latestCommit)
 | 
	
		
			
			| 79 | 114 |  			if err != nil {
 | 
	
		
			
			| 80 | 115 |  				return err
 | 
	
		
			
			| 81 | 116 |  			}
 | 
	
		
			
			| 82 | 117 |  
 | 
	
		
			
			|  | 118 | +			// Get a map containing the latest known content of each added,
 | 
	
		
			
			|  | 119 | +			// modified and removed file.
 | 
	
		
			
			| 83 | 120 |  			mergedContents := mergeContents(modified, removed, filesContents, previousFilesContents)
 | 
	
		
			
			|  | 121 | +			// Push the contents of the files that were added or modified to the
 | 
	
		
			
			|  | 122 | +			// Grafana API.
 | 
	
		
			
			| 84 | 123 |  			common.PushFiles(modified, mergedContents, client)
 | 
	
		
			
			| 85 | 124 |  
 | 
	
		
			
			|  | 125 | +			// If the user requested it, delete all dashboards that were removed
 | 
	
		
			
			|  | 126 | +			// from the repository.
 | 
	
		
			
			| 86 | 127 |  			if delRemoved {
 | 
	
		
			
			| 87 | 128 |  				common.DeleteDashboards(removed, mergedContents, client)
 | 
	
		
			
			| 88 | 129 |  			}
 | 
	
	
		
			
			|  | @@ -99,22 +140,36 @@ func poller(
 | 
	
		
			
			| 99 | 140 |  			}
 | 
	
		
			
			| 100 | 141 |  		}
 | 
	
		
			
			| 101 | 142 |  
 | 
	
		
			
			|  | 143 | +		// Update the commit and files contents to prepare for the next iteration.
 | 
	
		
			
			| 102 | 144 |  		previousCommit = latestCommit
 | 
	
		
			
			| 103 | 145 |  		previousFilesContents = filesContents
 | 
	
		
			
			|  | 146 | +
 | 
	
		
			
			|  | 147 | +		// Sleep before the next iteration.
 | 
	
		
			
			| 104 | 148 |  		time.Sleep(time.Duration(cfg.Pusher.Config.Interval) * time.Second)
 | 
	
		
			
			| 105 | 149 |  	}
 | 
	
		
			
			| 106 | 150 |  }
 | 
	
		
			
			| 107 | 151 |  
 | 
	
		
			
			|  | 152 | +// mergeContents will take as arguments a list of names of files that have been
 | 
	
		
			
			|  | 153 | +// added/modified, a list of names of files that have been removed from the Git
 | 
	
		
			
			|  | 154 | +// repository, the current contents of the files in the Git repository, and the
 | 
	
		
			
			|  | 155 | +// contents of the files in the Git repository as they were at the previous
 | 
	
		
			
			|  | 156 | +// iteration of the poller's loop.
 | 
	
		
			
			|  | 157 | +// It will create and return a map contaning the current content of all
 | 
	
		
			
			|  | 158 | +// added/modified file, and the previous content of all removed file (since
 | 
	
		
			
			|  | 159 | +// they are no longer accessible on disk). All files in this map is either added,
 | 
	
		
			
			|  | 160 | +// modified or removed on the Git repository.
 | 
	
		
			
			| 108 | 161 |  func mergeContents(
 | 
	
		
			
			| 109 | 162 |  	modified []string, removed []string,
 | 
	
		
			
			| 110 | 163 |  	filesContents map[string][]byte, previousFilesContents map[string][]byte,
 | 
	
		
			
			| 111 | 164 |  ) (merged map[string][]byte) {
 | 
	
		
			
			| 112 | 165 |  	merged = make(map[string][]byte)
 | 
	
		
			
			| 113 | 166 |  
 | 
	
		
			
			|  | 167 | +	// Load the added/modified files' contents
 | 
	
		
			
			| 114 | 168 |  	for _, modifiedFile := range modified {
 | 
	
		
			
			| 115 | 169 |  		merged[modifiedFile] = filesContents[modifiedFile]
 | 
	
		
			
			| 116 | 170 |  	}
 | 
	
		
			
			| 117 | 171 |  
 | 
	
		
			
			|  | 172 | +	// Load the removed files' contents
 | 
	
		
			
			| 118 | 173 |  	for _, removedFile := range removed {
 | 
	
		
			
			| 119 | 174 |  		merged[removedFile] = previousFilesContents[removedFile]
 | 
	
		
			
			| 120 | 175 |  	}
 |