Skip to content

Commit 20e8351

Browse files
authored
Avoid hardcoded links in READMEs (#938)
Added the option to add a YAML file containing links definitions. These definitions will be used to render "_dev/build/docs/*.md" files that use the new placeholder "url". The path to this file can be defined using the ELASTIC_PACKAGE_LINKS_FILE_PATH env var. By default, if that env var is not defined, it is looked at the root of the repository a file named "links_table.yml". Updated documentation about README to include this new "url" placeholder.
1 parent 35cfe28 commit 20e8351

File tree

20 files changed

+824
-48
lines changed

20 files changed

+824
-48
lines changed

cmd/profiles.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/elastic/elastic-package/internal/cobraext"
1818
"github.com/elastic/elastic-package/internal/configuration/locations"
19+
"github.com/elastic/elastic-package/internal/environment"
1920
"github.com/elastic/elastic-package/internal/profile"
2021
)
2122

@@ -26,7 +27,7 @@ const jsonFormat = "json"
2627
const tableFormat = "table"
2728

2829
// profileNameEnvVar is the name of the environment variable to set the default profile
29-
const profileNameEnvVar = "ELASTIC_PACKAGE_PROFILE"
30+
var profileNameEnvVar = environment.WithElasticPackagePrefix("PROFILE")
3031

3132
func setupProfilesCommand() *cobraext.Command {
3233
profilesLongDescription := `Use this command to add, remove, and manage multiple config profiles.

docs/howto/add_package_readme.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Files in `_dev/build/docs/*.md` follow Markdown syntax and they are rendered usi
1717
In addition to Markdown syntax, there are some additional placeholders that can be used in order to help
1818
to complete the information shown to the user.
1919

20+
2021
## Placeholders
2122

2223
List of placeholders that can be used in the Markdown templates:
@@ -81,3 +82,62 @@ List of placeholders that can be used in the Markdown templates:
8182
| data_stream.type | Data stream type. | constant_keyword | | |
8283
...
8384
```
85+
- `url <link_key>`: this placeholder is replaced by the URL defined in the `links_table.yml` file.
86+
- Example of usage:
87+
```
88+
Check {{ url "foo" }}
89+
```
90+
- Let's assume that there exists a file `links_table.yml` with these contents:
91+
```
92+
links:
93+
foo: http://url.com.test
94+
help: http://other.url.com/help
95+
```
96+
- Rendered output:
97+
```
98+
Check http://url.com.test
99+
```
100+
- Requirements:
101+
- It is needed to define a file with the links definitions. More information [in this section](#requirements)
102+
- `url <link_key> <link_text>`: this placeholder is replaced by the URL defined in the `links_table.yml` file and it creates a
103+
markdown link as `[link_text](url)`
104+
- Example of usage:
105+
```
106+
Check {{ url "help" "help guide" }}
107+
```
108+
- Let's assume that there exists a file `links_table.yml` with these contents:
109+
```
110+
links:
111+
foo: http://url.com.test
112+
help: http://other.url.com/help
113+
```
114+
- Rendered output:
115+
```
116+
Check [help guide](http://other.url.com/help)
117+
```
118+
- Requirements:
119+
- It is needed to define a file with the links definitions. More information [in this section](#requirements)
120+
121+
## Requirements
122+
123+
### Links definitions file
124+
125+
In order to be able to use `url` placeholder, links must be defined in a file.
126+
127+
This file by default is located at the root of the repository with the name `links_table.yml`.
128+
It can be overwritten by setting the environment variable `ELASTIC_PACKAGE_LINKS_FILE_PATH` with
129+
the path to corresponding YAML file.
130+
131+
The format of this file must be:
132+
```
133+
links:
134+
<key_1>: <url_1>
135+
<key_2>: <url_2>
136+
<key_3>: <url_3>
137+
```
138+
139+
As these links are rendered in building time (running `elastic-package build` command), there are some
140+
considerations that developers need to take into account:
141+
- packages will be built using always the links defined in the given file (e.g. `links_table.yml`).
142+
- any needed change in links requires that a new version of the package must be published to update the
143+
corresponding README file.

internal/builder/packages.go

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ func copyLicenseTextFile(licensePath string) error {
267267
}
268268

269269
func createBuildDirectory(dirs ...string) (string, error) {
270-
dir, err := findRepositoryRootDirectory()
270+
dir, err := files.FindRepositoryRootDirectory()
271271
if errors.Is(err, os.ErrNotExist) {
272272
return "", errors.New("package can be only built inside of a Git repository (.git folder is used as reference point)")
273273
}
@@ -287,40 +287,17 @@ func createBuildDirectory(dirs ...string) (string, error) {
287287
return buildDir, nil
288288
}
289289

290-
func findRepositoryRootDirectory() (string, error) {
291-
workDir, err := os.Getwd()
292-
if err != nil {
293-
return "", errors.Wrap(err, "locating working directory failed")
294-
}
295-
296-
dir := workDir
297-
for dir != "." {
298-
path := filepath.Join(dir, ".git")
299-
fileInfo, err := os.Stat(path)
300-
if err == nil && fileInfo.IsDir() {
301-
return dir, nil
302-
}
303-
304-
if dir == "/" {
305-
break
306-
}
307-
dir = filepath.Dir(dir)
308-
}
309-
310-
return "", os.ErrNotExist
311-
}
312-
313290
func findRepositoryLicense() (string, error) {
314-
dir, err := findRepositoryRootDirectory()
291+
dir, err := files.FindRepositoryRootDirectory()
315292
if err != nil {
316293
return "", err
317294
}
318295

319-
sourceLicensePath := filepath.Join(dir, licenseTextFileName)
320-
_, err = os.Stat(sourceLicensePath)
296+
sourceFileName := filepath.Join(dir, licenseTextFileName)
297+
_, err = os.Stat(sourceFileName)
321298
if err != nil {
322299
return "", err
323300
}
324301

325-
return sourceLicensePath, nil
302+
return sourceFileName, nil
326303
}

internal/configuration/locations/locations.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ import (
1010
"path/filepath"
1111

1212
"github.com/pkg/errors"
13+
14+
"github.com/elastic/elastic-package/internal/environment"
1315
)
1416

1517
const (
16-
// elasticPackageDataHome is the name of the environment variable used to override data folder for elastic-package
17-
elasticPackageDataHome = "ELASTIC_PACKAGE_DATA_HOME"
18-
1918
elasticPackageDir = ".elastic-package"
2019
stackDir = "stack"
2120
packagesDir = "development"
@@ -32,6 +31,9 @@ const (
3231
)
3332

3433
var (
34+
// elasticPackageDataHome is the name of the environment variable used to override data folder for elastic-package
35+
elasticPackageDataHome = environment.WithElasticPackagePrefix("DATA_HOME")
36+
3537
serviceLogsDir = filepath.Join(temporaryDir, "service_logs")
3638
kubernetesDeployerDir = filepath.Join(deployerDir, "kubernetes")
3739
terraformDeployerDir = filepath.Join(deployerDir, "terraform")

internal/docs/links_map.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package docs
6+
7+
import (
8+
"fmt"
9+
"os"
10+
"path/filepath"
11+
12+
"github.com/pkg/errors"
13+
"gopkg.in/yaml.v3"
14+
15+
"github.com/elastic/elastic-package/internal/environment"
16+
"github.com/elastic/elastic-package/internal/files"
17+
"github.com/elastic/elastic-package/internal/logger"
18+
)
19+
20+
const linksMapFileNameDefault = "links_table.yml"
21+
22+
var linksMapFilePathEnvVar = environment.WithElasticPackagePrefix("LINKS_FILE_PATH")
23+
24+
type linkMap struct {
25+
Links map[string]string `yaml:"links"`
26+
}
27+
28+
type linkOptions struct {
29+
caption string
30+
}
31+
32+
func newLinkMap() linkMap {
33+
var links linkMap
34+
links.Links = make(map[string]string)
35+
return links
36+
}
37+
38+
func (l linkMap) Get(key string) (string, error) {
39+
if url, ok := l.Links[key]; ok {
40+
return url, nil
41+
}
42+
return "", errors.Errorf("link key not found: %s", key)
43+
}
44+
45+
func (l linkMap) Add(key, value string) error {
46+
if _, ok := l.Links[key]; ok {
47+
return errors.Errorf("link key already present: %s", key)
48+
}
49+
l.Links[key] = value
50+
return nil
51+
}
52+
53+
func readLinksMap() (linkMap, error) {
54+
linksFilePath, err := linksDefinitionsFilePath()
55+
if err != nil {
56+
return linkMap{}, errors.Wrap(err, "locating links file failed")
57+
}
58+
59+
links := newLinkMap()
60+
if linksFilePath == "" {
61+
return links, nil
62+
}
63+
64+
logger.Debugf("Using links definitions file: %s", linksFilePath)
65+
contents, err := os.ReadFile(linksFilePath)
66+
if err != nil {
67+
return linkMap{}, errors.Wrapf(err, "readfile failed (path: %s)", linksFilePath)
68+
}
69+
70+
err = yaml.Unmarshal(contents, &links)
71+
if err != nil {
72+
return linkMap{}, err
73+
}
74+
75+
return links, nil
76+
}
77+
78+
func (l linkMap) RenderLink(key string, options linkOptions) (string, error) {
79+
url, err := l.Get(key)
80+
if err != nil {
81+
return "", err
82+
}
83+
if options.caption != "" {
84+
url = fmt.Sprintf("[%s](%s)", options.caption, url)
85+
}
86+
return url, nil
87+
}
88+
89+
// linksDefinitionsFilePath returns the path where links definitions are located or empty string if the file does not exist.
90+
// If linksMapFilePathEnvVar is defined, it returns the value of that env var.
91+
func linksDefinitionsFilePath() (string, error) {
92+
var err error
93+
linksFilePath, ok := os.LookupEnv(linksMapFilePathEnvVar)
94+
if ok {
95+
_, err = os.Stat(linksFilePath)
96+
if err != nil {
97+
// if env var is defined, file must exist
98+
return "", fmt.Errorf("links definitions file set with %s doesn't exist: %s", linksMapFilePathEnvVar, linksFilePath)
99+
}
100+
return linksFilePath, nil
101+
}
102+
103+
dir, err := files.FindRepositoryRootDirectory()
104+
if err != nil {
105+
return "", err
106+
}
107+
108+
linksFilePath = filepath.Join(dir, linksMapFileNameDefault)
109+
_, err = os.Stat(linksFilePath)
110+
if err != nil {
111+
logger.Debugf("links definitions default file doesn't exist: %s", linksFilePath)
112+
return "", nil
113+
}
114+
115+
return linksFilePath, nil
116+
117+
}

0 commit comments

Comments
 (0)