-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
config.go
148 lines (130 loc) · 4.39 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package main
import (
"crypto/sha256"
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/stephank/lazyssh/providers"
"golang.org/x/crypto/ssh"
)
// hclFiles is a File index expected by the DiagnosticWriter.
type hclFiles map[string]*hcl.File
// hclConfig is used to unmarshal the HCL top-level.
type hclConfig struct {
Server hclServerConfig `hcl:"server,block"`
Targets []hclTargetConfig `hcl:"target,block"`
}
// hclServerConfig is used to unmarshal the HCL `server` block.
type hclServerConfig struct {
Listen string `hcl:"listen,optional"`
HostKey string `hcl:"host_key,attr"`
AuthorizedKey string `hcl:"authorized_key,attr"`
}
// hclTargetConfig is used to unmarshal HCL `target` blocks.
type hclTargetConfig struct {
Addr string `hcl:"addr,label"`
Type string `hcl:"type,label"`
hcl.Body `hcl:"body,remain"`
}
// config is the result of parsing and validation the HCL configuration.
type config struct {
Listen string
HostKey ssh.Signer
AuthorizedKey [32]byte
providers.Providers
}
// Parse a file containing HCL configuration.
//
// This method returns a hclFiles used in printing diagnostics, the *config
// which is non-nil on success, and Diagnostics which may be non-nil on even
// when successful.
func parseConfigFile(cfgFile string, factories providers.Factories) (hclFiles, *config, hcl.Diagnostics) {
// Step one: basic HCL parsing, without schema.
parser := hclparse.NewParser()
file, diags := parser.ParseHCLFile(cfgFile)
files := parser.Files()
if diags.HasErrors() {
// Can't provide more info if this doesn't succeed.
return files, nil, diags
}
// Step two: Partial unmarshal using hclConfig and implied schema.
// Specifically, this does not unmarshal 'target' blocks.
hclConfig := hclConfig{}
if diags = gohcl.DecodeBody(file.Body, nil, &hclConfig); diags.HasErrors() {
// Can't provide more info if this doesn't succeed.
return files, nil, diags
}
// Step three: Defaults and further field parsing.
//
// If these fail, we add diagnostics but continue to provide more feedback.
if hclConfig.Server.Listen == "" {
hclConfig.Server.Listen = "localhost:7922"
}
hostKey, err := ssh.ParsePrivateKey([]byte(hclConfig.Server.HostKey))
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Could not parse server host_key",
Detail: err.Error(),
})
}
authorizedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(hclConfig.Server.AuthorizedKey))
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Could not parse server authorized_key",
Detail: err.Error(),
})
}
// Step four: For each 'target', ask the Factory for the associated type to
// parse config and instantiate a Provider.
//
// If these fail, we add diagnostics but continue to provide more feedback.
providers := make(providers.Providers)
for _, hclTarget := range hclConfig.Targets {
_, exists := providers[hclTarget.Addr]
if exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate target address",
Detail: fmt.Sprintf("Each target must have a unique address, but '%s' was used in multiple target definitions", hclTarget.Addr),
})
}
factory, ok := factories[hclTarget.Type]
if !ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider type",
Detail: fmt.Sprintf("Target '%s' has invalid provider type '%s'", hclTarget.Addr, hclTarget.Type),
})
continue
}
prov, err := factory.NewProvider(hclTarget.Addr, hclTarget.Body)
provDiags, ok := err.(hcl.Diagnostics)
if !ok && err != nil {
provDiags = hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Provider configuration error",
Detail: fmt.Sprintf("Error in '%s' provider configuration for target '%s': %s", hclTarget.Type, hclTarget.Addr, err.Error()),
},
}
}
diags = append(diags, provDiags...)
if !provDiags.HasErrors() {
providers[hclTarget.Addr] = prov
}
}
// Make sure we return nil Config if there are any errors.
if diags.HasErrors() {
return files, nil, diags
}
cfg := &config{
Listen: hclConfig.Server.Listen,
HostKey: hostKey,
AuthorizedKey: sha256.Sum256(authorizedKey.Marshal()),
Providers: providers,
}
return files, cfg, diags
}