Skip to content
This repository was archived by the owner on Jun 30, 2025. It is now read-only.

Commit 35508b1

Browse files
authored
26 improve binary mask feature (#29)
* update binary mask * add support to public repos * add template var
1 parent febfeb5 commit 35508b1

File tree

4 files changed

+238
-105
lines changed

4 files changed

+238
-105
lines changed

action.yaml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,23 @@ action:
1111
title: Password
1212
type: string
1313
default: ""
14+
- name: target
15+
title: Target version
16+
description: Specific version to install
17+
default: ""
1418
- name: config
1519
title: Config file
1620
description: Use specified config with metadata for update
1721
default: ""
18-
- name: target
19-
title: Target version
20-
description: Specific version to install
22+
- name: repository-url
23+
title: Repository URL
24+
description: URL do download binary from
25+
default: ""
26+
- name: release-file-mask
27+
title: Release file mask
28+
description: Specify URL for pinned release file with version. Available template vars are [URL, Version, OS, Arch, Ext]. Example - {{.URL}}/release
29+
default: ""
30+
- name: bin-mask
31+
title: Binary file mask
32+
description: Specify URL download mask for binary. Available template vars are [URL, Version, OS, Arch, Ext] Example - {{.URL}}/{{.Version}}
2133
default: ""

config.go

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package plasmactlupdate
22

33
import (
4+
"bytes"
45
"fmt"
6+
"net/url"
57
"os"
68
"path/filepath"
7-
"reflect"
9+
"strings"
10+
"text/template"
811

912
"github.com/launchrctl/launchr"
1013
"gopkg.in/yaml.v3"
1114
)
1215

16+
const (
17+
defaultPinnedReleaseTpl = "{{.URL}}/stable_release"
18+
defaultBinTpl = "{{.URL}}/{{.Version}}/{{.Name}}_{{.OS}}_{{.Arch}}{{.Ext}}"
19+
)
20+
1321
type config struct {
1422
RepositoryURL string `yaml:"repository_url"`
1523
PinnedRelease string `yaml:"pinned_release_file"`
@@ -24,9 +32,13 @@ func SetUpdateConfig(cfg *config) {
2432
updateConfig = cfg
2533
}
2634

27-
// GetDefaultConfig returns the global default configuration
35+
// GetDefaultConfig returns the global default configuration copy
2836
func getUpdateConfig() *config {
29-
return updateConfig
37+
if updateConfig == nil {
38+
return &config{}
39+
}
40+
cfgCopy := *updateConfig
41+
return &cfgCopy
3042
}
3143

3244
// LoadConfigFromBytesAndSet parses the configuration from a byte slice and sets it as the global default configuration.
@@ -68,21 +80,70 @@ func parseConfigFromBytes(data []byte) (*config, error) {
6880

6981
// validateConfig checks if all fields are filled and not empty
7082
func validateConfig(cfg *config) error {
71-
v := reflect.ValueOf(cfg).Elem()
72-
t := reflect.TypeOf(cfg).Elem()
73-
74-
for i := 0; i < v.NumField(); i++ {
75-
field := v.Field(i)
76-
fieldType := t.Field(i)
77-
78-
// Check if field is empty
79-
if field.Kind() == reflect.String && field.String() == "" {
80-
yamlTag := fieldType.Tag.Get("yaml")
81-
if yamlTag == "" {
82-
yamlTag = fieldType.Name
83-
}
84-
return fmt.Errorf("field '%s' is required and cannot be empty", yamlTag)
85-
}
83+
if cfg.RepositoryURL == "" {
84+
return fmt.Errorf("field 'repository_url' is required and cannot be empty")
85+
}
86+
87+
return nil
88+
}
89+
90+
type templateVars struct {
91+
URL string
92+
Name string
93+
Version string
94+
OS string
95+
Arch string
96+
Ext string
97+
}
98+
99+
// formatURL formats a template string with the provided variables
100+
func formatURL(templateStr string, vars templateVars) (string, error) {
101+
tmpl, err := template.New("url").Parse(templateStr)
102+
if err != nil {
103+
return "", fmt.Errorf("failed to parse template: %w", err)
104+
}
105+
106+
var buf bytes.Buffer
107+
err = tmpl.Execute(&buf, vars)
108+
if err != nil {
109+
return "", fmt.Errorf("failed to execute template: %w", err)
110+
}
111+
112+
result := strings.TrimSpace(buf.String())
113+
114+
// Validate the resulting URL
115+
if err = validateURL(result); err != nil {
116+
return "", fmt.Errorf("invalid URL generated from template: %w", err)
117+
}
118+
119+
return result, nil
120+
}
121+
122+
// validateURL checks if the URL is valid and doesn't contain unwanted characters
123+
func validateURL(rawURL string) error {
124+
// Check for newlines, tabs, and other control characters
125+
if strings.ContainsAny(rawURL, "\n\r\t\v\f") {
126+
return fmt.Errorf("URL contains invalid control characters")
127+
}
128+
129+
// Parse the URL to ensure it's valid
130+
parsedURL, err := url.Parse(rawURL)
131+
if err != nil {
132+
return fmt.Errorf("invalid URL format: %w", err)
133+
}
134+
135+
// Check if scheme is present and valid
136+
if parsedURL.Scheme == "" {
137+
return fmt.Errorf("URL missing scheme (http/https)")
138+
}
139+
140+
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
141+
return fmt.Errorf("invalid URL scheme: %s (expected http or https)", parsedURL.Scheme)
142+
}
143+
144+
// Check if host is present
145+
if parsedURL.Host == "" {
146+
return fmt.Errorf("URL missing host")
86147
}
87148

88149
return nil

plugin.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package plasmactlupdate
44
import (
55
"context"
66
_ "embed"
7+
"fmt"
78

89
"github.com/launchrctl/keyring"
910
"github.com/launchrctl/launchr"
@@ -61,12 +62,45 @@ func (p *Plugin) DiscoverActions(_ context.Context) ([]*action.Action, error) {
6162
return err
6263
}
6364

65+
var cfg *config
66+
// Check if the user submitted a custom config file.
67+
externalCfg := input.Opt("config").(string)
68+
if externalCfg != "" {
69+
cfgExternal, err := parseConfigFromPath(externalCfg)
70+
if err != nil {
71+
return fmt.Errorf("error parsing external config file %s: %v", externalCfg, err)
72+
}
73+
cfg = cfgExternal
74+
} else {
75+
cfg = getUpdateConfig()
76+
// Update the config with the user input if present.
77+
repoURL := input.Opt("repository-url").(string)
78+
if repoURL != "" {
79+
cfg.RepositoryURL = repoURL
80+
}
81+
pinnedRelease := input.Opt("release-file-mask").(string)
82+
if pinnedRelease != "" {
83+
cfg.PinnedRelease = pinnedRelease
84+
}
85+
binMask := input.Opt("bin-mask").(string)
86+
if binMask != "" {
87+
cfg.BinMask = binMask
88+
}
89+
}
90+
91+
// Fallback to default config values if they are empty.
92+
if cfg.PinnedRelease == "" {
93+
cfg.PinnedRelease = defaultPinnedReleaseTpl
94+
}
95+
if cfg.BinMask == "" {
96+
cfg.BinMask = defaultBinTpl
97+
}
98+
6499
u := &updateAction{
65100
k: p.k,
66101
credentials: ci,
67102
sudoCmd: cmd,
68-
cfg: getUpdateConfig(),
69-
externalCfg: input.Opt("config").(string),
103+
cfg: cfg,
70104
targetVersion: input.Opt("target").(string),
71105
}
72106
u.SetLogger(log)

0 commit comments

Comments
 (0)