mark/main.go

690 lines
18 KiB
Go
Raw Normal View History

2015-09-19 23:51:49 +06:00
package main
import (
2019-08-02 22:58:08 +03:00
"bytes"
2025-01-09 18:39:18 -06:00
"crypto/sha1"
"encoding/hex"
2015-09-19 23:51:49 +06:00
"fmt"
"os"
"path/filepath"
2025-01-09 18:39:18 -06:00
"regexp"
"slices"
2023-08-09 13:06:31 +02:00
"strings"
"time"
2015-09-19 23:51:49 +06:00
"github.com/bmatcuk/doublestar/v4"
2020-11-03 17:12:51 +03:00
"github.com/kovetskiy/lorg"
2024-09-26 15:24:39 +02:00
"github.com/kovetskiy/mark/attachment"
"github.com/kovetskiy/mark/confluence"
"github.com/kovetskiy/mark/includes"
"github.com/kovetskiy/mark/macro"
mark "github.com/kovetskiy/mark/markdown"
"github.com/kovetskiy/mark/metadata"
"github.com/kovetskiy/mark/page"
"github.com/kovetskiy/mark/stdlib"
"github.com/kovetskiy/mark/vfs"
2019-04-08 22:44:27 +03:00
"github.com/reconquest/karma-go"
2020-11-03 17:12:51 +03:00
"github.com/reconquest/pkg/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
2015-09-19 23:51:49 +06:00
)
const (
2025-02-19 10:55:40 +01:00
version = "12.1.0"
usage = "A tool for updating Atlassian Confluence pages from markdown."
description = `Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark`
2015-09-19 23:51:49 +06:00
)
var flags = []cli.Flag{
altsrc.NewStringFlag(&cli.StringFlag{
2023-05-03 15:59:18 +02:00
Name: "files",
Aliases: []string{"f"},
Value: "",
Usage: "use specified markdown file(s) for converting to html. Supports file globbing patterns (needs to be quoted).",
TakesFile: true,
EnvVars: []string{"MARK_FILES"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "compile-only",
Value: false,
Usage: "show resulting HTML and don't update Confluence page content.",
EnvVars: []string{"MARK_COMPILE_ONLY"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "dry-run",
Value: false,
Usage: "resolve page and ancestry, show resulting HTML and exit.",
EnvVars: []string{"MARK_DRY_RUN"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "edit-lock",
Value: false,
Aliases: []string{"k"},
Usage: "lock page editing to current user only to prevent accidental manual edits over Confluence Web UI.",
EnvVars: []string{"MARK_EDIT_LOCK"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "drop-h1",
Value: false,
2023-04-20 12:43:08 +02:00
Aliases: []string{"h1_drop"},
Usage: "don't include the first H1 heading in Confluence output.",
EnvVars: []string{"MARK_H1_DROP"},
}),
2023-09-06 16:19:09 -07:00
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "strip-linebreaks",
Value: false,
Aliases: []string{"L"},
Usage: "remove linebreaks inside of tags, to accomodate non-standard Confluence behavior",
2024-12-04 17:25:00 +01:00
EnvVars: []string{"MARK_STRIP_LINEBREAKS"},
2023-09-06 16:19:09 -07:00
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "title-from-h1",
Value: false,
2023-04-20 12:43:08 +02:00
Aliases: []string{"h1_title"},
Usage: "extract page title from a leading H1 heading. If no H1 heading on a page exists, then title must be set in the page metadata.",
EnvVars: []string{"MARK_H1_TITLE"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "title-append-generated-hash",
Value: false,
Usage: "appends a short hash generated from the path of the page (space, parents, and title) to the title",
EnvVars: []string{"MARK_TITLE_APPEND_GENERATED_HASH"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "minor-edit",
Value: false,
Usage: "don't send notifications while updating Confluence page.",
EnvVars: []string{"MARK_MINOR_EDIT"},
}),
2024-04-10 10:45:16 +01:00
altsrc.NewStringFlag(&cli.StringFlag{
Name: "version-message",
Value: "",
2024-04-11 19:39:11 +02:00
Usage: "add a message to the page version, to explain the edit (default: \"\")",
2024-04-10 10:45:16 +01:00
EnvVars: []string{"MARK_VERSION_MESSAGE"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "color",
Value: "auto",
Usage: "display logs in color. Possible values: auto, never.",
EnvVars: []string{"MARK_COLOR"},
}),
2024-12-30 21:40:33 +02:00
altsrc.NewStringFlag(&cli.StringFlag{
Name: "log-level",
Value: "info",
Usage: "set the log level. Possible values: TRACE, DEBUG, INFO, WARNING, ERROR, FATAL.",
EnvVars: []string{"MARK_LOG_LEVEL"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Value: "",
Usage: "use specified username for updating Confluence page.",
EnvVars: []string{"MARK_USERNAME"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Value: "",
Usage: "use specified token for updating Confluence page. Specify - as password to read password from stdin, or your Personal access token. Username is not mandatory if personal access token is provided. For more info please see: https://developer.atlassian.com/server/confluence/confluence-server-rest-api/#authentication.",
EnvVars: []string{"MARK_PASSWORD"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "target-url",
Aliases: []string{"l"},
Value: "",
Usage: "edit specified Confluence page. If -l is not specified, file should contain metadata (see above).",
EnvVars: []string{"MARK_TARGET_URL"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "base-url",
2023-04-20 12:43:08 +02:00
Aliases: []string{"b", "base_url"},
Value: "",
Usage: "base URL for Confluence. Alternative option for base_url config field.",
EnvVars: []string{"MARK_BASE_URL"},
}),
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
2023-09-07 17:35:06 +02:00
Value: configFilePath(),
Usage: "use the specified configuration file.",
TakesFile: true,
EnvVars: []string{"MARK_CONFIG"},
},
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "ci",
Value: false,
Usage: "run on CI mode. It won't fail if files are not found.",
EnvVars: []string{"MARK_CI"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "space",
Value: "",
Usage: "use specified space key. If the space key is not specified, it must be set in the page metadata.",
EnvVars: []string{"MARK_SPACE"},
}),
2023-08-09 13:06:31 +02:00
altsrc.NewStringFlag(&cli.StringFlag{
Name: "parents",
Value: "",
2023-11-22 15:11:55 +00:00
Usage: "A list containing the parents of the document separated by parents-delimiter (default: '/'). These will be prepended to the ones defined in the document itself.",
2023-08-09 13:06:31 +02:00
EnvVars: []string{"MARK_PARENTS"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "parents-delimiter",
Value: "/",
Usage: "The delimiter used for the parents list",
EnvVars: []string{"MARK_PARENTS_DELIMITER"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "mermaid-provider",
Value: "cloudscript",
Usage: "defines the mermaid provider to use. Supported options are: cloudscript, mermaid-go.",
EnvVars: []string{"MARK_MERMAID_PROVIDER"},
}),
altsrc.NewFloat64Flag(&cli.Float64Flag{
Name: "mermaid-scale",
Value: 1.0,
Usage: "defines the scaling factor for mermaid renderings.",
EnvVars: []string{"MARK_MERMAID_SCALE"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "include-path",
Value: "",
Usage: "Path for shared includes, used as a fallback if the include doesn't exist in the current directory.",
TakesFile: true,
EnvVars: []string{"MARK_INCLUDE_PATH"},
}),
2025-01-09 18:39:18 -06:00
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "changes-only",
Value: false,
Usage: "Avoids re-uploading pages that haven't changed since the last run.",
EnvVars: []string{"MARK_CHANGES_ONLY"},
2025-01-09 18:39:18 -06:00
}),
}
2015-09-19 23:51:49 +06:00
func main() {
app := &cli.App{
Name: "mark",
Usage: usage,
Description: description,
Version: version,
Flags: flags,
Before: altsrc.InitInputSourceWithContext(flags,
func(context *cli.Context) (altsrc.InputSourceContext, error) {
if context.IsSet("config") {
filePath := context.String("config")
return altsrc.NewTomlSourceFromFile(filePath)
} else {
// Fall back to default if config is unset and path exists
2023-09-07 17:35:06 +02:00
_, err := os.Stat(configFilePath())
if os.IsNotExist(err) {
return &altsrc.MapInputSource{}, nil
}
2023-09-07 17:35:06 +02:00
return altsrc.NewTomlSourceFromFile(configFilePath())
}
}),
EnableBashCompletion: true,
HideHelpCommand: true,
2023-08-09 13:06:31 +02:00
Action: RunMark,
2015-09-19 23:51:49 +06:00
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
2021-04-02 13:15:54 -04:00
}
}
func RunMark(cCtx *cli.Context) error {
2024-12-30 21:40:33 +02:00
if err := setLogLevel(cCtx); err != nil {
return err
2020-11-03 17:12:51 +03:00
}
if cCtx.String("color") == "never" {
log.GetLogger().SetFormat(
lorg.NewFormat(
`${time:2006-01-02 15:04:05.000} ${level:%s:left:true} ${prefix}%s`,
),
)
log.GetLogger().SetOutput(os.Stderr)
}
creds, err := GetCredentials(cCtx.String("username"), cCtx.String("password"), cCtx.String("target-url"), cCtx.String("base-url"), cCtx.Bool("compile-only"))
2019-07-31 01:35:17 +03:00
if err != nil {
return err
}
2019-08-02 22:58:08 +03:00
api := confluence.NewAPI(creds.BaseURL, creds.Username, creds.Password)
files, err := doublestar.FilepathGlob(cCtx.String("files"))
2021-04-02 13:15:54 -04:00
if err != nil {
return err
2021-04-02 13:15:54 -04:00
}
if len(files) == 0 {
msg := "No files matched"
if cCtx.Bool("ci") {
log.Warning(msg)
} else {
log.Fatal(msg)
}
2021-04-02 13:15:54 -04:00
}
2023-05-03 15:59:18 +02:00
log.Debug("config:")
for _, f := range cCtx.Command.Flags {
flag := f.Names()
if flag[0] == "password" {
log.Debugf(nil, "%20s: %v", flag[0], "******")
} else {
log.Debugf(nil, "%20s: %v", flag[0], cCtx.Value(flag[0]))
}
}
2021-04-02 13:15:54 -04:00
// Loop through files matched by glob pattern
for _, file := range files {
log.Infof(
nil,
"processing %s",
2021-04-02 13:15:54 -04:00
file,
)
target := processFile(file, api, cCtx, creds.PageID, creds.Username)
2021-04-02 13:15:54 -04:00
if target != nil && !(cCtx.Bool("dry-run") || cCtx.Bool("compile-only")) { // on dry-run or compile-only, the target is nil
log.Infof(
nil,
"page successfully updated: %s",
creds.BaseURL+target.Links.Full,
)
fmt.Println(creds.BaseURL + target.Links.Full)
}
2021-04-02 13:15:54 -04:00
}
return nil
2021-04-02 13:15:54 -04:00
}
func processFile(
file string,
api *confluence.API,
cCtx *cli.Context,
pageID string,
username string,
) *confluence.PageInfo {
2023-01-03 18:54:04 +01:00
markdown, err := os.ReadFile(file)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
2015-09-20 00:58:39 +06:00
}
2021-11-08 20:15:59 +06:00
markdown = bytes.ReplaceAll(markdown, []byte("\r\n"), []byte("\n"))
2023-08-09 13:06:31 +02:00
parents := strings.Split(cCtx.String("parents"), cCtx.String("parents-delimiter"))
meta, markdown, err := metadata.ExtractMeta(markdown, cCtx.String("space"), cCtx.Bool("title-from-h1"), parents, cCtx.Bool("title-append-generated-hash"))
2019-08-02 22:58:08 +03:00
if err != nil {
log.Fatal(err)
2015-09-20 00:58:39 +06:00
}
2022-04-22 15:26:25 +06:00
if pageID != "" && meta != nil {
log.Warning(
`specified file contains metadata, ` +
`but it will be ignored due specified command line URL`,
)
meta = nil
}
if pageID == "" && meta == nil {
2022-01-18 12:10:46 +06:00
log.Fatal(
2023-03-20 19:56:02 +01:00
`specified file doesn't contain metadata ` +
`and URL is not specified via command line ` +
`or doesn't contain pageId GET-parameter`,
2022-01-18 12:10:46 +06:00
)
}
2023-03-20 19:56:02 +01:00
if meta.Space == "" {
2023-03-20 22:54:11 +01:00
log.Fatal(
"space is not set ('Space' header is not set and '--space' option is not set)",
)
}
if meta.Title == "" {
log.Fatal(
`page title is not set ('Title' header is not set ` +
`and '--title-from-h1' option and 'h1_title' config is not set or there is no H1 in the file)`,
)
}
2019-08-02 22:58:08 +03:00
stdlib, err := stdlib.New(api)
2015-09-19 23:51:49 +06:00
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
2015-09-19 23:51:49 +06:00
}
2019-08-02 22:58:08 +03:00
templates := stdlib.Templates
var recurse bool
for {
templates, markdown, recurse, err = includes.ProcessIncludes(
filepath.Dir(file),
cCtx.String("include-path"),
2019-08-02 22:58:08 +03:00
markdown,
templates,
)
if err != nil {
log.Fatal(err)
}
if !recurse {
break
}
}
2022-02-02 16:03:06 +06:00
macros, markdown, err := macro.ExtractMacros(
filepath.Dir(file),
cCtx.String("include-path"),
2022-02-02 16:03:06 +06:00
markdown,
templates,
)
2019-08-02 22:58:08 +03:00
if err != nil {
log.Fatal(err)
}
macros = append(macros, stdlib.Macros...)
for _, macro := range macros {
markdown, err = macro.Apply(markdown)
if err != nil {
log.Fatal(err)
}
}
links, err := page.ResolveRelativeLinks(api, meta, markdown, filepath.Dir(file), cCtx.String("space"), cCtx.Bool("title-from-h1"), parents, cCtx.Bool("title-append-generated-hash"))
if err != nil {
log.Fatalf(err, "unable to resolve relative links")
}
2020-12-04 00:28:52 +03:00
2024-09-26 15:24:39 +02:00
markdown = page.SubstituteLinks(markdown, links)
if cCtx.Bool("dry-run") {
2024-09-26 15:24:39 +02:00
_, _, err := page.ResolvePage(cCtx.Bool("dry-run"), api, meta)
if err != nil {
log.Fatalf(err, "unable to resolve page location")
}
}
if cCtx.Bool("compile-only") || cCtx.Bool("dry-run") {
if cCtx.Bool("drop-h1") {
log.Info(
"the leading H1 heading will be excluded from the Confluence output",
)
}
2023-09-06 16:19:09 -07:00
html, _ := mark.CompileMarkdown(markdown, stdlib, file, cCtx.String("mermaid-provider"), cCtx.Float64("mermaid-scale"), cCtx.Bool("drop-h1"), cCtx.Bool("strip-linebreaks"))
fmt.Println(html)
return nil
2019-08-02 22:58:08 +03:00
}
2019-04-08 22:44:27 +03:00
var target *confluence.PageInfo
if meta != nil {
2024-09-26 15:24:39 +02:00
parent, page, err := page.ResolvePage(cCtx.Bool("dry-run"), api, meta)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatalf(
karma.Describe("title", meta.Title).Reason(err),
"unable to resolve %s",
meta.Type,
2019-04-19 10:31:41 +03:00
)
}
if page == nil {
page, err = api.CreatePage(
meta.Space,
meta.Type,
parent,
meta.Title,
``,
)
if err != nil {
log.Fatalf(
err,
"can't create %s %q",
meta.Type,
meta.Title,
)
}
// (issues/139): A delay between the create and update call
// helps mitigate a 409 conflict that can occur when attempting
// to update a page just after it was created.
time.Sleep(1 * time.Second)
}
target = page
} else {
2021-04-02 13:15:54 -04:00
if pageID == "" {
2019-04-19 10:31:41 +03:00
log.Fatalf(nil, "URL should provide 'pageId' GET-parameter")
}
2021-04-02 13:15:54 -04:00
page, err := api.GetPageByID(pageID)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatalf(err, "unable to retrieve page by id")
}
target = page
}
// Resolve attachments created from <!-- Attachment: --> directive
2023-09-01 22:59:04 +02:00
localAttachments, err := attachment.ResolveLocalAttachments(vfs.LocalOS, filepath.Dir(file), meta.Attachments)
if err != nil {
log.Fatalf(err, "unable to locate attachments")
}
2023-09-01 22:59:04 +02:00
attaches, err := attachment.ResolveAttachments(
2022-01-18 12:10:46 +06:00
api,
target,
localAttachments,
2022-01-18 12:10:46 +06:00
)
2019-04-19 17:35:05 +03:00
if err != nil {
log.Fatalf(err, "unable to create/update attachments")
}
2019-04-19 10:31:41 +03:00
2023-09-01 22:59:04 +02:00
markdown = attachment.CompileAttachmentLinks(markdown, attaches)
2019-04-20 10:24:30 +03:00
if cCtx.Bool("drop-h1") {
log.Info(
"the leading H1 heading will be excluded from the Confluence output",
)
}
2023-09-06 16:19:09 -07:00
html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, file, cCtx.String("mermaid-provider"), cCtx.Float64("mermaid-scale"), cCtx.Bool("drop-h1"), cCtx.Bool("strip-linebreaks"))
// Resolve attachements detected from markdown
2023-09-01 22:59:04 +02:00
_, err = attachment.ResolveAttachments(
api,
target,
inlineAttachments,
)
if err != nil {
log.Fatalf(err, "unable to create/update attachments")
}
2019-04-20 10:24:30 +03:00
2019-08-02 22:58:08 +03:00
{
var buffer bytes.Buffer
err := stdlib.Templates.ExecuteTemplate(
&buffer,
"ac:layout",
struct {
2021-06-17 14:56:27 -04:00
Layout string
Sidebar string
Body string
2019-08-02 22:58:08 +03:00
}{
2021-06-17 14:56:27 -04:00
Layout: meta.Layout,
Sidebar: meta.Sidebar,
Body: html,
2019-08-02 22:58:08 +03:00
},
)
if err != nil {
log.Fatal(err)
}
html = buffer.String()
}
2025-01-09 18:39:18 -06:00
var finalVersionMessage string
var shouldUpdatePage bool = true
if cCtx.Bool("changes-only") {
contentHash := getSHA1Hash(html)
log.Debugf(
2025-01-09 18:44:25 -06:00
nil,
"content hash: %s",
contentHash,
2025-01-09 18:39:18 -06:00
)
versionPattern := `\[v([a-f0-9]{40})]$`
re := regexp.MustCompile(versionPattern)
2025-01-09 18:39:18 -06:00
matches := re.FindStringSubmatch(target.Version.Message)
if len(matches) > 1 {
log.Debugf(
2025-01-09 18:44:25 -06:00
nil,
"previous content hash: %s",
matches[1],
2025-01-09 18:39:18 -06:00
)
if matches[1] == contentHash {
log.Infof(
2025-01-09 18:44:25 -06:00
nil,
"page %q is already up to date",
target.Title,
2025-01-09 18:39:18 -06:00
)
shouldUpdatePage = false
}
}
finalVersionMessage = fmt.Sprintf("%s [v%s]", cCtx.String("version-message"), contentHash)
} else {
finalVersionMessage = cCtx.String("version-message")
}
if shouldUpdatePage {
err = api.UpdatePage(target, html, cCtx.Bool("minor-edit"), finalVersionMessage, meta.Labels, meta.ContentAppearance, meta.Emoji)
2025-01-09 18:39:18 -06:00
if err != nil {
log.Fatal(err)
}
2015-09-19 23:51:49 +06:00
}
updateLabels(api, target, meta)
if cCtx.Bool("edit-lock") {
2019-04-19 10:31:41 +03:00
log.Infof(
nil,
`edit locked on page %q by user %q to prevent manual edits`,
target.Title,
2021-04-02 13:15:54 -04:00
username,
)
err := api.RestrictPageUpdates(target, username)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
}
}
2021-04-02 13:15:54 -04:00
return target
2015-09-19 23:51:49 +06:00
}
2023-09-07 17:35:06 +02:00
2024-09-26 15:24:39 +02:00
func updateLabels(api *confluence.API, target *confluence.PageInfo, meta *metadata.Meta) {
labelInfo, err := api.GetPageLabels(target, "global")
if err != nil {
log.Fatal(err)
}
log.Debug("Page Labels:")
log.Debug(labelInfo.Labels)
log.Debug("Meta Labels:")
log.Debug(meta.Labels)
delLabels := determineLabelsToRemove(labelInfo, meta)
log.Debug("Del Labels:")
log.Debug(delLabels)
addLabels := determineLabelsToAdd(meta, labelInfo)
log.Debug("Add Labels:")
log.Debug(addLabels)
if len(addLabels) > 0 {
_, err = api.AddPageLabels(target, addLabels)
if err != nil {
log.Fatal(err)
}
}
for _, label := range delLabels {
_, err = api.DeletePageLabel(target, label)
if err != nil {
log.Fatal(err)
}
}
}
// Page has label but label not in Metadata
2024-09-26 15:24:39 +02:00
func determineLabelsToRemove(labelInfo *confluence.LabelInfo, meta *metadata.Meta) []string {
var labels []string
for _, label := range labelInfo.Labels {
if !slices.ContainsFunc(meta.Labels, func(metaLabel string) bool {
return strings.EqualFold(metaLabel, label.Name)
}) {
labels = append(labels, label.Name)
}
}
return labels
}
// Metadata has label but Page does not have it
2024-09-26 15:24:39 +02:00
func determineLabelsToAdd(meta *metadata.Meta, labelInfo *confluence.LabelInfo) []string {
var labels []string
for _, metaLabel := range meta.Labels {
if !slices.ContainsFunc(labelInfo.Labels, func(label confluence.Label) bool {
return strings.EqualFold(label.Name, metaLabel)
}) {
labels = append(labels, metaLabel)
}
}
return labels
}
2023-09-07 17:35:06 +02:00
func configFilePath() string {
fp, err := os.UserConfigDir()
if err != nil {
log.Fatal(err)
}
return filepath.Join(fp, "mark")
}
2024-12-30 21:40:33 +02:00
func setLogLevel(cCtx *cli.Context) error {
logLevel := cCtx.String("log-level")
2025-01-09 21:03:15 +02:00
switch strings.ToUpper(logLevel) {
case lorg.LevelTrace.String():
2024-12-30 21:40:33 +02:00
log.SetLevel(lorg.LevelTrace)
2025-01-09 21:03:15 +02:00
case lorg.LevelDebug.String():
2024-12-30 21:40:33 +02:00
log.SetLevel(lorg.LevelDebug)
2025-01-09 21:03:15 +02:00
case lorg.LevelInfo.String():
2024-12-30 21:40:33 +02:00
log.SetLevel(lorg.LevelInfo)
2025-01-09 21:03:15 +02:00
case lorg.LevelWarning.String():
2024-12-30 21:40:33 +02:00
log.SetLevel(lorg.LevelWarning)
2025-01-09 21:03:15 +02:00
case lorg.LevelError.String():
2024-12-30 21:40:33 +02:00
log.SetLevel(lorg.LevelError)
2025-01-09 21:03:15 +02:00
case lorg.LevelFatal.String():
2024-12-30 21:40:33 +02:00
log.SetLevel(lorg.LevelFatal)
default:
return fmt.Errorf("unknown log level: %s", logLevel)
}
log.GetLevel()
return nil
}
2025-01-09 18:39:18 -06:00
func getSHA1Hash(input string) string {
hash := sha1.New()
hash.Write([]byte(input))
return hex.EncodeToString(hash.Sum(nil))
2025-01-13 19:09:29 +01:00
}