Use yaml for config, build filename and slug using configurable templates
parent
442395a83c
commit
cbf53ccb23
155
config.go
155
config.go
|
@ -2,8 +2,9 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/caarlos0/env/v6"
|
"gopkg.in/yaml.v2"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +18,8 @@ var (
|
||||||
SelectedCdn Cdn
|
SelectedCdn Cdn
|
||||||
SelectedNotificationServices NotificationServices
|
SelectedNotificationServices NotificationServices
|
||||||
SelectedImageCompression ImageCompression
|
SelectedImageCompression ImageCompression
|
||||||
|
DefaultLanguage string
|
||||||
|
Languages map[string]Language
|
||||||
)
|
)
|
||||||
|
|
||||||
type SyndicationTarget struct {
|
type SyndicationTarget struct {
|
||||||
|
@ -24,41 +27,109 @@ type SyndicationTarget struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type config struct {
|
type YamlConfig struct {
|
||||||
BlogUrl string `env:"BLOG_URL,required"`
|
BlogUrl string `yaml:"blogUrl"`
|
||||||
BaseUrl string `env:"BASE_URL,required"`
|
BaseUrl string `yaml:"baseUrl"`
|
||||||
MediaUrl string `env:"MEDIA_URL"`
|
MediaUrl string `yaml:"mediaUrl"`
|
||||||
GitFilepath string `env:"GIT_FILEPATH" envDefault:"/tmp/micropubrepo"`
|
DefaultLanguage string `yaml:"defaultLang"`
|
||||||
GitUrl string `env:"GIT_URL"`
|
Languages map[string]Language `yaml:"languages"`
|
||||||
GitUsername string `env:"GIT_USERNAME"`
|
Git GitConfig `yaml:"git"`
|
||||||
GitPassword string `env:"GIT_PASSWORD"`
|
BunnyCdn BunnyCdnConfig `yaml:"bunnyCdn"`
|
||||||
GitAuthorName string `env:"GIT_AUTHOR_NAME" envDefault:"hugo-micropub"`
|
Telegram TelegramConfig `yaml:"telegram"`
|
||||||
GitAuthorEmail string `env:"GIT_AUTHOR_EMAIL" envDefault:"hugo-micropub@example.com"`
|
Tinify TinifyConfig `yaml:"tinify"`
|
||||||
BunnyCdnKey string `env:"BUNNY_CDN_KEY"`
|
IgnoredWebmentionUrls []string `yaml:"ignoreWebmention"`
|
||||||
BunnyCdnStorageKey string `env:"BUNNY_CDN_STORAGE_KEY"`
|
SyndicationTargets []string `yaml:"syndication"`
|
||||||
BunnyCdnStorageName string `env:"BUNNY_CDN_STORAGE_NAME"`
|
}
|
||||||
TelegramUserId int `env:"TELEGRAM_USER_ID"`
|
|
||||||
TelegramBotToken string `env:"TELEGRAM_BOT_TOKEN"`
|
type BunnyCdnConfig struct {
|
||||||
IgnoredWebmentionUrls []string `env:"WEBMENTION_IGNORED" envSeparator:","`
|
Key string `yaml:"key"`
|
||||||
SyndicationTargets []string `env:"SYNDICATION" envSeparator:","`
|
StorageKey string `yaml:"storageKey"`
|
||||||
TinifyKey string `env:"TINIFY_KEY"`
|
StorageName string `yaml:"storageName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelegramConfig struct {
|
||||||
|
UserId int `yaml:"userId"`
|
||||||
|
BotToken string `yaml:"botToken"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TinifyConfig struct {
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GitConfig struct {
|
||||||
|
Filepath string `yaml:"filepath"`
|
||||||
|
Url string `yaml:"url"`
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
AuthorName string `yaml:"authorName"`
|
||||||
|
AuthorEmail string `yaml:"authorEmail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Language struct {
|
||||||
|
BlogUrl string `yaml:"blogUrl"`
|
||||||
|
ContentDir string `yaml:"contentDir"`
|
||||||
|
DefaultSection string `yaml:"defaultSection"`
|
||||||
|
Sections map[string]Section `yaml:"sections"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Section struct {
|
||||||
|
FilenameTemplate string `yaml:"file"`
|
||||||
|
LocationTemplate string `yaml:"location"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig() (err error) {
|
func initConfig() (err error) {
|
||||||
cfg := config{}
|
configFileName, configSet := os.LookupEnv("CONFIG")
|
||||||
if err := env.Parse(&cfg); err != nil {
|
if !configSet {
|
||||||
return errors.New("failed to parse config, probably not all required env vars set")
|
configFileName = "config.yml"
|
||||||
|
}
|
||||||
|
configFile, err := os.Open(configFileName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to open config file")
|
||||||
|
}
|
||||||
|
cfg := YamlConfig{}
|
||||||
|
err = yaml.NewDecoder(configFile).Decode(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to parse yaml")
|
||||||
}
|
}
|
||||||
// Blog URL (required)
|
// Blog URL (required)
|
||||||
|
if len(cfg.BlogUrl) < 1 {
|
||||||
|
return errors.New("blogUrl not configured")
|
||||||
|
}
|
||||||
if !strings.HasSuffix(cfg.BlogUrl, "/") {
|
if !strings.HasSuffix(cfg.BlogUrl, "/") {
|
||||||
return errors.New("missing trailing slash in BLOG_URL")
|
return errors.New("missing trailing slash in configured blogUrl")
|
||||||
}
|
}
|
||||||
BlogUrl = cfg.BlogUrl
|
BlogUrl = cfg.BlogUrl
|
||||||
// Media endpoint
|
// Media endpoint (required)
|
||||||
|
if len(cfg.BaseUrl) < 1 {
|
||||||
|
return errors.New("baseUrl not configured")
|
||||||
|
}
|
||||||
|
if len(cfg.MediaUrl) < 1 {
|
||||||
|
return errors.New("mediaUrl not configured")
|
||||||
|
}
|
||||||
if !strings.HasSuffix(cfg.BaseUrl, "/") {
|
if !strings.HasSuffix(cfg.BaseUrl, "/") {
|
||||||
return errors.New("missing trailing slash in BASE_URL")
|
return errors.New("missing trailing slash in configured baseUrl")
|
||||||
}
|
}
|
||||||
MediaEndpointUrl = cfg.BaseUrl + "media"
|
MediaEndpointUrl = cfg.BaseUrl + "media"
|
||||||
|
// Languages (required)
|
||||||
|
if len(cfg.DefaultLanguage) < 1 {
|
||||||
|
return errors.New("no default language configured")
|
||||||
|
}
|
||||||
|
DefaultLanguage = cfg.DefaultLanguage
|
||||||
|
if len(cfg.Languages) > 0 {
|
||||||
|
for _, lang := range cfg.Languages {
|
||||||
|
if len(lang.ContentDir) < 1 || len(lang.DefaultSection) < 1 || len(lang.Sections) < 1 {
|
||||||
|
return errors.New("language not completely configured")
|
||||||
|
}
|
||||||
|
for _, section := range lang.Sections {
|
||||||
|
if len(section.FilenameTemplate) < 1 || len(section.LocationTemplate) < 1 {
|
||||||
|
return errors.New("section not completely configured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Languages = cfg.Languages
|
||||||
|
} else {
|
||||||
|
return errors.New("no languages configured")
|
||||||
|
}
|
||||||
// Ignored Webmention URLs (optional)
|
// Ignored Webmention URLs (optional)
|
||||||
IgnoredWebmentionUrls = cfg.IgnoredWebmentionUrls
|
IgnoredWebmentionUrls = cfg.IgnoredWebmentionUrls
|
||||||
// Syndication Targets (optional)
|
// Syndication Targets (optional)
|
||||||
|
@ -73,14 +144,14 @@ func initConfig() (err error) {
|
||||||
// Find selected storage
|
// Find selected storage
|
||||||
SelectedStorage = func() Storage {
|
SelectedStorage = func() Storage {
|
||||||
// Git
|
// Git
|
||||||
if len(cfg.GitFilepath) > 0 && len(cfg.GitUrl) > 0 && len(cfg.GitUsername) > 0 && len(cfg.GitPassword) > 0 && len(cfg.GitAuthorName) > 0 && len(cfg.GitAuthorEmail) > 0 {
|
if len(cfg.Git.Filepath) > 0 && len(cfg.Git.Url) > 0 && len(cfg.Git.Username) > 0 && len(cfg.Git.Password) > 0 && len(cfg.Git.AuthorName) > 0 && len(cfg.Git.AuthorEmail) > 0 {
|
||||||
return &Git{
|
return &Git{
|
||||||
filepath: cfg.GitFilepath,
|
filepath: cfg.Git.Filepath,
|
||||||
url: cfg.GitUrl,
|
url: cfg.Git.Url,
|
||||||
username: cfg.GitUsername,
|
username: cfg.Git.Username,
|
||||||
password: cfg.GitPassword,
|
password: cfg.Git.Password,
|
||||||
name: cfg.GitAuthorName,
|
name: cfg.Git.AuthorName,
|
||||||
email: cfg.GitAuthorEmail,
|
email: cfg.Git.AuthorEmail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -92,10 +163,10 @@ func initConfig() (err error) {
|
||||||
SelectedMediaStorage = func() MediaStorage {
|
SelectedMediaStorage = func() MediaStorage {
|
||||||
// BunnyCDN
|
// BunnyCDN
|
||||||
// MEDIA_URL needs trailing slash too
|
// MEDIA_URL needs trailing slash too
|
||||||
if len(cfg.BunnyCdnStorageKey) > 0 && len(cfg.BunnyCdnStorageName) > 0 && len(cfg.MediaUrl) > 0 && strings.HasSuffix(cfg.MediaUrl, "/") {
|
if len(cfg.BunnyCdn.StorageKey) > 0 && len(cfg.BunnyCdn.StorageName) > 0 && len(cfg.MediaUrl) > 0 && strings.HasSuffix(cfg.MediaUrl, "/") {
|
||||||
return &BunnyCdnStorage{
|
return &BunnyCdnStorage{
|
||||||
key: cfg.BunnyCdnStorageKey,
|
key: cfg.BunnyCdn.StorageKey,
|
||||||
storageZoneName: cfg.BunnyCdnStorageName,
|
storageZoneName: cfg.BunnyCdn.StorageName,
|
||||||
baseLocation: cfg.MediaUrl,
|
baseLocation: cfg.MediaUrl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,8 +178,8 @@ func initConfig() (err error) {
|
||||||
// Find selected CDN (optional)
|
// Find selected CDN (optional)
|
||||||
SelectedCdn = func() Cdn {
|
SelectedCdn = func() Cdn {
|
||||||
// BunnyCDN
|
// BunnyCDN
|
||||||
if len(cfg.BunnyCdnKey) > 0 {
|
if len(cfg.BunnyCdn.Key) > 0 {
|
||||||
return &BunnyCdn{key: cfg.BunnyCdnKey}
|
return &BunnyCdn{key: cfg.BunnyCdn.Key}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
|
@ -119,10 +190,10 @@ func initConfig() (err error) {
|
||||||
SelectedNotificationServices = func() NotificationServices {
|
SelectedNotificationServices = func() NotificationServices {
|
||||||
var notificationServices []NotificationService = nil
|
var notificationServices []NotificationService = nil
|
||||||
// Telegram
|
// Telegram
|
||||||
if cfg.TelegramUserId > 0 && len(cfg.TelegramBotToken) > 0 {
|
if cfg.Telegram.UserId > 0 && len(cfg.Telegram.BotToken) > 0 {
|
||||||
notificationServices = append(notificationServices, &Telegram{
|
notificationServices = append(notificationServices, &Telegram{
|
||||||
userId: cfg.TelegramUserId,
|
userId: cfg.Telegram.UserId,
|
||||||
botToken: cfg.TelegramBotToken,
|
botToken: cfg.Telegram.BotToken,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return notificationServices
|
return notificationServices
|
||||||
|
@ -133,9 +204,9 @@ func initConfig() (err error) {
|
||||||
// Find configured image compression service (optional)
|
// Find configured image compression service (optional)
|
||||||
SelectedImageCompression = func() ImageCompression {
|
SelectedImageCompression = func() ImageCompression {
|
||||||
// Tinify
|
// Tinify
|
||||||
if len(cfg.TinifyKey) > 0 {
|
if len(cfg.Tinify.Key) > 0 {
|
||||||
return &Tinify{
|
return &Tinify{
|
||||||
key: cfg.TinifyKey,
|
key: cfg.Tinify.Key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
64
entry.go
64
entry.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -258,26 +259,57 @@ func computeExtraSettings(entry *Entry) error {
|
||||||
random := generateRandomString(now, 5)
|
random := generateRandomString(now, 5)
|
||||||
entry.slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
|
entry.slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
|
||||||
}
|
}
|
||||||
// Compute filename and location
|
// Set language
|
||||||
contentFolder := "content"
|
if len(entry.language) == 0 {
|
||||||
localizedBlogUrl := BlogUrl
|
entry.language = DefaultLanguage
|
||||||
if len(entry.language) > 0 && entry.language != "en" {
|
|
||||||
// Append language to content folder: "content-de" for language "de"
|
|
||||||
contentFolder += "-" + entry.language
|
|
||||||
// Append language to BlogUrl
|
|
||||||
localizedBlogUrl += entry.language + "/"
|
|
||||||
}
|
}
|
||||||
if len(entry.section) < 1 {
|
// Compute filename and location
|
||||||
entry.section = "micro"
|
lang := Languages[entry.language]
|
||||||
|
contentFolder := lang.ContentDir
|
||||||
|
localizedBlogUrl := BlogUrl
|
||||||
|
if len(lang.BlogUrl) != 0 {
|
||||||
|
localizedBlogUrl = lang.BlogUrl
|
||||||
|
}
|
||||||
|
if len(entry.section) == 0 {
|
||||||
|
entry.section = lang.DefaultSection
|
||||||
}
|
}
|
||||||
entry.section = strings.ToLower(entry.section)
|
entry.section = strings.ToLower(entry.section)
|
||||||
if entry.section == "thoughts" || entry.section == "links" || entry.section == "micro" {
|
section := lang.Sections[entry.section]
|
||||||
entry.filename = fmt.Sprintf("%v/%v/%02d/%02d/%v.md", contentFolder, entry.section, now.Year(), int(now.Month()), entry.slug)
|
pathVars := struct {
|
||||||
entry.location = fmt.Sprintf("%v%v/%02d/%02d/%v/", localizedBlogUrl, entry.section, now.Year(), int(now.Month()), entry.slug)
|
LocalContentFolder string
|
||||||
} else {
|
LocalBlogUrl string
|
||||||
entry.filename = fmt.Sprintf("%v/%v/%v.md", contentFolder, entry.section, entry.slug)
|
Year int
|
||||||
entry.location = fmt.Sprintf("%v%v/%v/", localizedBlogUrl, entry.section, entry.slug)
|
Month int
|
||||||
|
Slug string
|
||||||
|
Section string
|
||||||
|
}{
|
||||||
|
LocalContentFolder: contentFolder,
|
||||||
|
LocalBlogUrl: localizedBlogUrl,
|
||||||
|
Year: now.Year(),
|
||||||
|
Month: int(now.Month()),
|
||||||
|
Slug: entry.slug,
|
||||||
|
Section: entry.section,
|
||||||
}
|
}
|
||||||
|
filenameTmpl, err := template.New("filename").Parse(section.FilenameTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to parse filename template")
|
||||||
|
}
|
||||||
|
filename := new(bytes.Buffer)
|
||||||
|
err = filenameTmpl.Execute(filename, pathVars)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to execute filename template")
|
||||||
|
}
|
||||||
|
entry.filename = filename.String()
|
||||||
|
locationTmpl, err := template.New("location").Parse(section.LocationTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to parse location template")
|
||||||
|
}
|
||||||
|
location := new(bytes.Buffer)
|
||||||
|
err = locationTmpl.Execute(location, pathVars)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to execute location template")
|
||||||
|
}
|
||||||
|
entry.location = location.String()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -4,7 +4,6 @@ go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
codeberg.org/jlelse/tinify v0.0.0-20200123222407-7fc9c21822b0
|
codeberg.org/jlelse/tinify v0.0.0-20200123222407-7fc9c21822b0
|
||||||
github.com/caarlos0/env/v6 v6.2.1
|
|
||||||
github.com/go-git/go-git/v5 v5.0.0
|
github.com/go-git/go-git/v5 v5.0.0
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
willnorris.com/go/webmention v0.0.0-20200126231626-5a55fff6bf71
|
willnorris.com/go/webmention v0.0.0-20200126231626-5a55fff6bf71
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -8,8 +8,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/caarlos0/env/v6 v6.2.1 h1:/bFpX1dg4TNioJjg7mrQaSrBoQvRfLUHNfXivdFbbEo=
|
|
||||||
github.com/caarlos0/env/v6 v6.2.1/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
@ -51,9 +49,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
||||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
|
|
Loading…
Reference in New Issue