func trackRepositoriesAction()

in internal/cli/praefect/subcmd_track_repositories.go [60:194]


func trackRepositoriesAction(ctx context.Context, cmd *cli.Command) error {
	logger := log.ConfigureCommand()

	conf, err := readConfig(cmd.String(configFlagName))
	if err != nil {
		return err
	}

	db, clean, err := openDB(conf.DB, cmd.ErrWriter)
	if err != nil {
		return fmt.Errorf("connect to database: %w", err)
	}
	defer clean()

	ctx = correlation.ContextWithCorrelation(ctx, correlation.SafeRandomID())
	logger = logger.WithField("correlation_id", correlation.ExtractFromContext(ctx))

	store := datastore.NewPostgresRepositoryStore(db, conf.StorageNames())

	inputPath := cmd.String(paramInputPath)
	f, err := os.Open(inputPath)
	if err != nil {
		return fmt.Errorf("open input: %w", err)
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)

	fmt.Fprintf(cmd.Writer, "Validating repository information in %q\n", inputPath)

	var requests []trackRepositoryRequest
	var line int
	var repoErrs []invalidRequest
	pathLines := make(map[string][]int)

	// Read in and validate all requests from input file before executing. This prevents us from
	// partially executing a file, which makes it difficult to tell which repos were actually
	// tracked.
	for scanner.Scan() {
		line++

		request := trackRepositoryRequest{}
		badReq := invalidRequest{line: line}

		if err := json.Unmarshal(scanner.Bytes(), &request); err != nil {
			badReq.errs = append(badReq.errs, err)
			repoErrs = append(repoErrs, badReq)

			// Invalid request, nothing to validate.
			continue
		}

		if request.RelativePath == "" {
			badReq.errs = append(badReq.errs, requiredParameterError(paramRelativePath))
		}
		badReq.relativePath = request.RelativePath

		if request.ReplicaPath == "" {
			badReq.errs = append(badReq.errs, requiredParameterError(paramReplicaPath))
		}
		badReq.replicaPath = request.ReplicaPath

		if request.VirtualStorage == "" {
			badReq.errs = append(badReq.errs, requiredParameterError(paramVirtualStorage))
		}
		if request.AuthoritativeStorage == "" {
			badReq.errs = append(badReq.errs, requiredParameterError(paramAuthoritativeStorage))
		}
		if len(badReq.errs) > 0 {
			repoErrs = append(repoErrs, badReq)

			// Incomplete request, no further validation possible.
			continue
		}

		// Repo paths are globally unique, any attempt to add the same path multiple virtual storages
		// is invalid and must be rejected.
		prevLines, exists := pathLines[request.RelativePath]
		if exists {
			badReq.errs = append(badReq.errs, &dupPathError{path: request.RelativePath})
			repoErrs = append(repoErrs, badReq)

			prevLines = append(prevLines, line)
			pathLines[request.RelativePath] = prevLines

			// We've already checked this path, no need to run further checks.
			continue
		}
		pathLines[request.RelativePath] = []int{line}

		repoInDB, err := store.RepositoryExists(ctx, request.VirtualStorage, request.RelativePath)
		if err != nil {
			// Bail out if we're having trouble contacting the DB, nothing is going to work if this fails.
			return fmt.Errorf("checking database: %w", err)
		}
		if repoInDB {
			badReq.errs = append(badReq.errs, fmt.Errorf("repository is already tracked by Praefect"))
			repoErrs = append(repoErrs, badReq)
			// Repo already in Praefect DB, we can skip it.
			continue
		}

		authoritativeRepoExists, err := request.authoritativeRepositoryExists(ctx, conf, logger, cmd.Writer, request.AuthoritativeStorage)
		if err != nil {
			badReq.errs = append(badReq.errs, fmt.Errorf("checking repository on disk: %w", err))
		} else if !authoritativeRepoExists {
			badReq.errs = append(badReq.errs, fmt.Errorf("not a valid git repository"))
		}

		if len(badReq.errs) > 0 {
			repoErrs = append(repoErrs, badReq)
			continue
		}
		requests = append(requests, request)
	}

	if len(repoErrs) > 0 {
		printInvalidRequests(cmd.Writer, repoErrs, pathLines, inputPath)
		return fmt.Errorf("invalid entries found, aborting")
	}
	if len(requests) == 0 {
		return fmt.Errorf("no repository information found in %q", inputPath)
	}

	fmt.Fprintf(cmd.Writer, "All repository details are correctly formatted\n")
	fmt.Fprintf(cmd.Writer, "Tracking %v repositories in Praefect DB...\n", line)
	replicateImmediately := cmd.Bool("replicate-immediately")
	for _, request := range requests {
		if err := request.execRequest(ctx, db, conf, cmd.Writer, logger, replicateImmediately); err != nil {
			return fmt.Errorf("tracking repository %q: %w", request.RelativePath, err)
		}
	}

	return nil
}