func editReleaseNote()

in cmd/krel/cmd/release_notes.go [1243:1401]


func editReleaseNote(pr int, workDir string, originalNote, modifiedNote *notes.ReleaseNote) (shouldRetryEditing bool, err error) {
	// To edit the note, we will create a yaml file, with the changed fields
	// active and we'll add the unaltered fields commented for the user to review

	modifiedFields := &notes.ReleaseNotesMap{PR: pr}
	unalteredFields := &notes.ReleaseNotesMap{PR: pr}
	numChanges := 0

	if originalNote.Text == modifiedNote.Text {
		unalteredFields.ReleaseNote.Text = &originalNote.Text
	} else {
		modifiedFields.ReleaseNote.Text = &modifiedNote.Text
		numChanges++
	}

	if originalNote.Author == modifiedNote.Author {
		unalteredFields.ReleaseNote.Author = &originalNote.Author
	} else {
		modifiedFields.ReleaseNote.Author = &modifiedNote.Author
		numChanges++
	}

	if fmt.Sprint(originalNote.SIGs) == fmt.Sprint(modifiedNote.SIGs) {
		unalteredFields.ReleaseNote.SIGs = &originalNote.SIGs
	} else {
		modifiedFields.ReleaseNote.SIGs = &modifiedNote.SIGs
		numChanges++
	}

	if fmt.Sprint(originalNote.Kinds) == fmt.Sprint(modifiedNote.Kinds) {
		unalteredFields.ReleaseNote.Kinds = &originalNote.Kinds
	} else {
		modifiedFields.ReleaseNote.Kinds = &modifiedNote.Kinds
		numChanges++
	}

	if fmt.Sprint(originalNote.Areas) == fmt.Sprint(modifiedNote.Areas) {
		unalteredFields.ReleaseNote.Areas = &originalNote.Areas
	} else {
		modifiedFields.ReleaseNote.Areas = &modifiedNote.Areas
		numChanges++
	}

	if fmt.Sprint(originalNote.Feature) == fmt.Sprint(modifiedNote.Feature) {
		unalteredFields.ReleaseNote.Feature = &originalNote.Feature
	} else {
		modifiedFields.ReleaseNote.Feature = &modifiedNote.Feature
		numChanges++
	}

	if fmt.Sprint(originalNote.ActionRequired) == fmt.Sprint(modifiedNote.ActionRequired) {
		unalteredFields.ReleaseNote.ActionRequired = &originalNote.ActionRequired
	} else {
		modifiedFields.ReleaseNote.ActionRequired = &modifiedNote.ActionRequired
		numChanges++
	}

	if fmt.Sprint(originalNote.DoNotPublish) == fmt.Sprint(modifiedNote.DoNotPublish) {
		unalteredFields.ReleaseNote.DoNotPublish = &originalNote.DoNotPublish
	} else {
		modifiedFields.ReleaseNote.DoNotPublish = &modifiedNote.DoNotPublish
		numChanges++
	}

	// TODO: Implement after writing a documentation comparison func
	unalteredFields.ReleaseNote.Documentation = &originalNote.Documentation

	// Create the release note map for the editor:
	output := "---\n" + string(mapEditingInstructions) + "\n"

	if numChanges == 0 {
		// If there are no changes, present the user with the commented
		// map with the original values
		yamlCode, err := yaml.Marshal(&unalteredFields)
		if err != nil {
			return false, errors.Wrap(err, "marshalling release note to map")
		}
		output += "# " + strings.ReplaceAll(string(yamlCode), "\n", "\n# ")
	} else {
		// ... otherwise build a mixed map with the changes and the original
		// values commented out for reference
		yamlCode, err := yaml.Marshal(&modifiedFields)
		if err != nil {
			return false, errors.Wrap(err, "marshalling release note to map")
		}

		unalteredYAML, err := yaml.Marshal(&unalteredFields.ReleaseNote)
		if err != nil {
			return false, errors.Wrap(err, "marshalling release note to map")
		}
		output += string(yamlCode) + " # " + strings.ReplaceAll(string(unalteredYAML), "\n", "\n # ")
	}

	kubeEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"})
	changes, tempFilePath, err := kubeEditor.LaunchTempFile("release-notes-map-", ".yaml", bytes.NewReader([]byte(output)))
	if err != nil {
		return false, errors.Wrap(err, "while launching editor")
	}

	defer func() {
		// Cleanup the temporary map file
		if err := os.Remove(tempFilePath); err != nil {
			logrus.Warn("could not remove temporary mapfile")
		}
	}()

	// If the map was not modified, we don't make any changes
	if string(changes) == output || len(changes) == 0 {
		logrus.Info("Release notes map was not modified")
		return false, nil
	}

	// If the yaml file is blank, return non error
	lines := strings.Split(string(changes), "\n")
	re := regexp.MustCompile(`^\s*#|^\s*$`)
	blankFile := true
	for _, line := range lines {
		// If only only one line is not blank/comment
		if line != "---" && !re.MatchString(line) {
			blankFile = false
			break
		}
	}

	if blankFile {
		logrus.Info("YAML mapfile is blank, ignoring")
		return false, nil
	}

	// Verify that the new yaml is valid and can be serialized back into a Map
	testMap := notes.ReleaseNotesMap{}
	err = yaml.Unmarshal(changes, &testMap)

	if err != nil {
		logrus.Error("The YAML code has errors")
		return true, errors.Wrap(err, "while verifying if changes are a valid map")
	}

	if testMap.PR == 0 {
		logrus.Error("The yaml code does not have a PR number")
		return true, errors.New("Invalid map: the YAML code did not have a PR number")
	}

	// Remarshall the newyaml to save only the new values
	newYAML, err := yaml.Marshal(testMap)
	if err != nil {
		return true, errors.Wrap(err, "while re-marshaling new map")
	}

	// Write the new map, removing the instructions
	mapPath := filepath.Join(workDir, mapsMainDirectory, fmt.Sprintf("pr-%d-map.yaml", pr))
	err = os.WriteFile(mapPath, newYAML, os.FileMode(0o644))
	if err != nil {
		logrus.Errorf("Error writing map to %s: %s", mapPath, err)
		return true, errors.Wrap(err, "writing modified release note map")
	}

	return false, nil
}