Example #1
0
func watch(
	client *consulapi.Client,
	prefix string,
	path string,
	token string,
	pairCh chan<- consulapi.KVPairs,
	errCh chan<- error,
	quitCh <-chan struct{}) {

	// Create the root for KVs, if necessary
	mkdirp.Mk(path, 0777)

	// Get the initial list of k/v pairs. We don't do a retryableList
	// here because we want a fast fail if the initial request fails.
	opts := &consulapi.QueryOptions{Token: token}
	pairs, meta, err := client.KV().List(prefix, opts)
	if err != nil {
		errCh <- err
		return
	}

	// Send the initial list out right away
	pairCh <- pairs

	// Loop forever (or until quitCh is closed) and watch the keys
	// for changes.
	curIndex := meta.LastIndex
	for {
		select {
		case <-quitCh:
			return
		default:
		}

		pairs, meta, err = retryableList(
			func() (consulapi.KVPairs, *consulapi.QueryMeta, error) {
				opts = &consulapi.QueryOptions{WaitIndex: curIndex, Token: token}
				return client.KV().List(prefix, opts)
			})

		if err != nil {
			// This happens when the connection to the consul agent dies.  Build in a retry by looping after a delay.
			log.Warn("Error communicating with consul agent.")
			continue
		}

		pairCh <- pairs
		log.WithFields(log.Fields{
			"curIndex":  curIndex,
			"lastIndex": meta.LastIndex,
		}).Debug("Potential index update observed")
		curIndex = meta.LastIndex
	}
}
Example #2
0
func main() {
	reader := bufio.NewReader(os.Stdin)

	fmt.Println("aiw3/maps 0.1.1")
	fmt.Print("Please type which kind of files you want to download [client/server]: ")
	cur_type, err := reader.ReadString('\n')
	cur_type = strings.Trim(cur_type, "\n\r")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if cur_type != "client" && cur_type != "server" {
		fmt.Println("Invalid type")
		os.Exit(1)
	}

	fmt.Println("Loading packages list...")

	resp, err := http.Get("https://cloudrack.io/aiw3/maps.json")
	if err != nil {
		fmt.Printf("Error while downloading map data; %s", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("Error while downloading map data; %s", err)
		os.Exit(1)
	}

	var maps Maps
	err = json.Unmarshal(body, &maps)
	if err != nil {
		fmt.Printf("Error while parsing map data; %s", err)
		os.Exit(1)
	}

	fmt.Println("Verifying repository's integrity...")
	for _, pkg := range maps.Packages {
		for name, _ := range pkg.Files {
			if _, exists := maps.Files[name]; !exists {
				fmt.Printf("Invalid repository; File %s does not exist (required by %s).\n", name, pkg.Id)
				os.Exit(1)
			}
		}
	}

	fmt.Println("Choose the map to install:")

	for i, pkg := range maps.Packages {
		fmt.Printf("[%d] %s (%s)\n", i, pkg.Name, pkg.Id)
	}

	fmt.Printf("[%d] All files (both client and server)\n", len(maps.Packages))

	fmt.Print("Type in the ID of the map you want to install: ")

	input, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	i, err := strconv.Atoi(strings.Trim(input, "\r\n"))
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if i > len(maps.Packages) || i < 0 {
		fmt.Println("Package does not exist.")
		os.Exit(1)
	}

	if i < len(maps.Packages) {
		pkg := maps.Packages[i]

		for name, side := range pkg.Files {
			if side == "shared" || side == cur_type {
				abs, err := filepath.Abs(name)
				if err != nil {
					fmt.Println(err)
					os.Exit(1)
				}

				if utility.FileExists(abs) {
					fmt.Println(name + " already exists")
					continue
				}

				dir := filepath.Dir(abs)

				err = mkdirp.Mk(dir, 755)
				if err != nil {
					fmt.Println(err)
					os.Exit(1)
				}

				fmt.Printf("Downloading %s\n", name)
				var cmd *exec.Cmd

				if runtime.GOOS == "windows" {
					cmd = exec.Command("megatools/megadl", "--path="+dir, maps.Files[name])
				} else {
					cmd = exec.Command("megadl", "--path="+dir, maps.Files[name])
				}

				cmd.Stdout = os.Stdout
				cmd.Stderr = os.Stderr
				err = cmd.Run()
				if err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
			}
		}
	} else {
		for name, url := range maps.Files {
			abs, err := filepath.Abs(name)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}

			if utility.FileExists(abs) {
				fmt.Println(name + " already exists")
				continue
			}

			dir := filepath.Dir(abs)

			err = mkdirp.Mk(dir, 755)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}

			fmt.Printf("Downloading %s\n", name)
			cmd := exec.Command("megatools/megadl", "--path="+dir, url)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			err = cmd.Run()
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		}
	}

	fmt.Println("Install completed.")
}
Example #3
0
// Connects to Consul and watches a given K/V prefix and uses that to
// write to the filesystem.
func watchMappingAndExec(config *WatchConfig, mappingConfig *MappingConfig) (int, error) {
	client, err := buildConsulClient(config.Consul)
	if err != nil {
		return 0, err
	}

	// If prefix starts with /, trim it.
	if mappingConfig.Prefix[0] == '/' {
		mappingConfig.Prefix = mappingConfig.Prefix[1:]
	}

	// If the config path is lacking a trailing separator, add it.
	if mappingConfig.Path[len(mappingConfig.Path)-1] != os.PathSeparator {
		mappingConfig.Path += string(os.PathSeparator)
	}

	isWindows := os.PathSeparator != '/'

	// Remove an unhandled trailing quote, which presented itself on Windows when
	// the given path contained spaces (requiring quotes) and also had a trailing
	// backslash.
	if mappingConfig.Path[len(mappingConfig.Path)-1] == 34 {
		mappingConfig.Path = mappingConfig.Path[:len(mappingConfig.Path)-1]
	}

	// Start the watcher goroutine that watches for changes in the
	// K/V and notifies us on a channel.
	errCh := make(chan error, 1)
	pairCh := make(chan consulapi.KVPairs)
	quitCh := make(chan struct{})

	// Defer close of quitCh if we're running more than once
	if !config.RunOnce {
		defer close(quitCh)
	}

	go watch(
		client, mappingConfig.Prefix, mappingConfig.Path, config.Consul.Token, pairCh, errCh, quitCh)

	var env map[string]string
	for {
		var pairs consulapi.KVPairs

		// Wait for new pairs to come on our channel or an error
		// to occur.
		select {
		case pairs = <-pairCh:
		case err := <-errCh:
			return 0, err
		}

		newEnv := make(map[string]string)
		for _, pair := range pairs {
			log.WithFields(log.Fields{
				"key": pair.Key,
			}).Debug("Key present in source")
			k := strings.TrimPrefix(pair.Key, mappingConfig.Prefix)
			k = strings.TrimLeft(k, "/")
			newEnv[k] = string(pair.Value)
		}

		// If the variables didn't actually change,
		// then don't do anything.
		if reflect.DeepEqual(env, newEnv) {
			continue
		}

		// Iterate over all objects in the current env.  If they are not in the newEnv, they
		// were deleted from Consul and should be deleted from disk.
		for k := range env {
			if _, ok := newEnv[k]; !ok {
				log.WithFields(log.Fields{
					"key": k,
				}).Debug("Key no longer present locally")
				// Write file to disk
				keyfile := fmt.Sprintf("%s%s", mappingConfig.Path, k)
				if isWindows {
					keyfile = strings.Replace(keyfile, "/", "\\", -1)
				}

				err := os.Remove(keyfile)
				if err != nil {
					log.WithFields(log.Fields{
						"error": err,
					}).Error("Failed to remove key")
				}
			}
		}

		// Replace the env so we can detect future changes
		env = newEnv

		// Write the updated keys to the filesystem at the specified path
		for k, v := range newEnv {
			// Write file to disk
			keyfile := fmt.Sprintf("%s%s", mappingConfig.Path, k)

			// if Windows, replace / with windows path delimiter
			if isWindows {
				keyfile = strings.Replace(keyfile, "/", "\\", -1)
				// mkdirp the file's path
				err := mkdirp.Mk(keyfile[:strings.LastIndex(keyfile, "\\")], 0777)
				if err != nil {
					log.WithFields(log.Fields{
						"error": err,
					}).Error("Failed to create parent directory for key")
				}
			} else {
				// mkdirp the file's path
				err := mkdirp.Mk(keyfile[:strings.LastIndex(keyfile, "/")], 0777)
				if err != nil {
					log.WithFields(log.Fields{
						"error": err,
					}).Error("Failed to create parent directory for key")
				}
			}

			f, err := os.Create(keyfile)
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					"file":  keyfile,
				}).Error("Failed to create file")
				continue
			}

			defer f.Close()

			log.WithFields(log.Fields{
				"length": len(v),
			}).Debug("Input value length")

			buff := new(bytes.Buffer)
			buff.Write([]byte(v))

			if len(mappingConfig.Keystore) > 0 {
				decryptedValue, err := gosecret.DecryptTags([]byte(v), mappingConfig.Keystore)
				if err != nil {
					log.WithFields(log.Fields{
						"error": err,
					}).Error("Failed to decrypt value")
					continue
				}

				log.WithFields(log.Fields{
					"length": len(decryptedValue),
				}).Debug("Output value length")

				data := string(decryptedValue)

				funcs := template.FuncMap{
					// Template functions
					"goDecrypt": goDecryptFunc(mappingConfig.Keystore),
				}

				tmpl, err := template.New("decryption").Funcs(funcs).Parse(data)
				if err != nil {
					log.WithFields(log.Fields{
						"error": err,
					}).Error("Could not parse template")
					continue
				}

				// Run the template to verify the output.
				buff = new(bytes.Buffer)
				err = tmpl.Execute(buff, nil)
				if err != nil {
					log.WithFields(log.Fields{
						"error": err,
					}).Error("Could not execute template")
					continue
				}
			}

			wrote, err := f.Write(buff.Bytes())
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					"file":  keyfile,
				}).Error("Failed to write to file")
				continue
			}

			log.WithFields(log.Fields{
				"length": wrote,
				"file":   keyfile,
			}).Debug("Successfully wrote value to file")

			err = f.Sync()
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					"file":  keyfile,
				}).Error("Failed to sync file")
			}

			err = f.Close()
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
					"file":  keyfile,
				}).Error("Failed to close file")
			}
		}

		// Configuration changed, run our onchange command, if one was specified.
		if mappingConfig.OnChange != nil {
			var cmd = exec.Command(mappingConfig.OnChange[0], mappingConfig.OnChange[1:]...)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr
			// Always wait for the forked process to exit.  We may wish to revisit this, but I think
			// it's the safest approach since it avoids a case where rapid key updates DOS a system
			// by slurping all proc handles.
			err = cmd.Run()

			if err != nil {
				return 111, err
			}
		}

		// If we are only running once, close the channel on this watcher.
		if config.RunOnce {
			close(quitCh)
			return 0, nil
		}
	}
}