-
Notifications
You must be signed in to change notification settings - Fork 157
/
server.go
210 lines (189 loc) · 7.11 KB
/
server.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package server
import (
"context"
"fmt"
"runtime/debug"
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/rpc/proto"
"github.com/1Password/shell-plugins/sdk/schema"
)
type errFunctionFieldNotSet struct {
objName string
funcName string
}
func (e errFunctionFieldNotSet) Error() string {
return fmt.Sprintf("field not set for %s.%s", e.objName, e.funcName)
}
// RPCServer makes a schema.Plugin available over RPC. Any method on this struct is available to a client over RPC.
//
// The schema.Plugin struct has various slices that contain functions or interfaces that should also be available
// over RPC. Since Go's rpc package does not support storing functions in structs, these functions and interfaces
// are removed from the schema.Plugin and stored in a map. These functions can then be called by making a separate
// RPC call that includes the address in this map.
type RPCServer struct {
p schema.Plugin
importers map[proto.CredentialID]sdk.Importer
provisioners map[proto.ProvisionerID]sdk.Provisioner
needsAuth map[proto.ExecutableID]sdk.NeedsAuthentication
}
func newServer(p schema.Plugin) *RPCServer {
s := &RPCServer{
importers: map[proto.CredentialID]sdk.Importer{},
provisioners: map[proto.ProvisionerID]sdk.Provisioner{},
needsAuth: map[proto.ExecutableID]sdk.NeedsAuthentication{},
}
// Remove all functions and interfaces from schema.Plugin and store them in the respective maps.
credentials := map[proto.CredentialID]*schema.CredentialType{}
for i := range p.Credentials {
credentials[proto.CredentialID(i)] = &p.Credentials[i]
}
for i := range p.Executables {
s.needsAuth[proto.ExecutableID(i)] = p.Executables[i].NeedsAuth
p.Executables[i].NeedsAuth = nil
for usageID, credentialUse := range p.Executables[i].Uses {
executableID := proto.ExecutableID(i)
s.provisioners[proto.ProvisionerID{
IsDefaultProvisioner: false,
CredentialUsage: proto.CredentialUsageID{
Executable: executableID,
Usage: usageID,
},
}] = credentialUse.Provisioner
p.Executables[i].Uses[usageID].Provisioner = nil
}
}
for id, c := range credentials {
s.importers[id] = c.Importer
c.Importer = nil
s.provisioners[proto.ProvisionerID{
IsDefaultProvisioner: true,
Credential: id,
}] = c.DefaultProvisioner
c.DefaultProvisioner = nil
}
s.p = p
return s
}
// GetPlugin returns the schema.Plugin for this RPCServer.
// All functions and interfaces in this plugin are set to nil. The caller of this function is responsible for
// replacing those values with an implementation that calls these functions over RPC.
func (t *RPCServer) GetPlugin(_ int, resp *proto.GetPluginResponse) error {
*resp = proto.GetPluginResponse{
CredentialHasImporter: map[proto.CredentialID]bool{},
ExecutableHasNeedAuth: map[proto.ExecutableID]bool{},
CredentialUsageHasProvisioner: map[proto.CredentialUsageID]bool{},
Plugin: t.p,
}
for executableID, needsAuth := range t.needsAuth {
resp.ExecutableHasNeedAuth[executableID] = needsAuth != nil
}
for credentialID, importer := range t.importers {
resp.CredentialHasImporter[credentialID] = importer != nil
}
for provisionerID, provisioner := range t.provisioners {
if !provisionerID.IsDefaultProvisioner {
resp.CredentialUsageHasProvisioner[provisionerID.CredentialUsage] = provisioner != nil
}
}
return nil
}
// ExecutableNeedsAuth is a remote version of the NeedsAuth function in schema.Executable.
// The call is forwarded to Executables[req.ExecutableID].NeedsAuth of the original plugin.
func (t *RPCServer) ExecutableNeedsAuth(req proto.ExecutableNeedsAuthRequest, resp *bool) error {
needsAuth, ok := t.needsAuth[req.ExecutableID]
if !ok || needsAuth == nil {
return &errFunctionFieldNotSet{
objName: req.ExecutableID.String(),
funcName: "NeedsAuth",
}
}
*resp = needsAuth(req.NeedsAuthenticationInput)
return nil
}
// CredentialImport is a remote version of the Import() function in schema.CredentialType.
// The call is forwarded to the Import() function of the credential identified by req.CredentialID.
func (t *RPCServer) CredentialImport(req proto.ImportCredentialRequest, resp *sdk.ImportOutput) error {
defer func() {
if err := recover(); err != nil {
diagnostics := getPanicDiagnostics(err)
resp.Attempts = []*sdk.ImportAttempt{
{
Candidates: nil,
Source: sdk.ImportSource{},
Diagnostics: diagnostics,
},
}
}
}()
importer, ok := t.importers[req.CredentialID]
if !ok || importer == nil {
return &errFunctionFieldNotSet{
objName: req.CredentialID.String(),
funcName: "Importer",
}
}
*resp = req.ImportOutput
importer(context.Background(), req.ImportInput, resp)
return nil
}
// CredentialProvisionerDescription is a remote version of the the Description() method of the sdk.Provisioner
// interface. The call is forwarded to the Description() function of the Provisioner of the credential identified by
// req.CredentialID.
func (t *RPCServer) CredentialProvisionerDescription(req proto.ProvisionerID, resp *string) error {
provisioner, err := t.getProvisioner(req)
if err != nil {
return err
}
*resp = provisioner.Description()
return nil
}
// CredentialProvisionerProvision is a remote version of the the Provision() method of the sdk.Provisioner
// interface. The call is forwarded to the Provision() function of the Provisioner of the credential identified by
// req.CredentialID.
func (t *RPCServer) CredentialProvisionerProvision(req proto.ProvisionCredentialRequest, resp *sdk.ProvisionOutput) error {
defer func() {
if err := recover(); err != nil {
diagnostics := getPanicDiagnostics(err)
resp.Diagnostics = diagnostics
}
}()
provisioner, err := t.getProvisioner(req.ProvisionerID)
if err != nil {
return err
}
*resp = req.ProvisionOutput
provisioner.Provision(context.Background(), req.ProvisionInput, resp)
return nil
}
// CredentialProvisionerDeprovision is a remote version of the the Deprovision() method of the sdk.Provisioner
// interface. The call is forwarded to the Deprovision() function of the Provisioner of the credential identified by
// req.CredentialID.
func (t *RPCServer) CredentialProvisionerDeprovision(req proto.DeprovisionCredentialRequest, resp *sdk.DeprovisionOutput) error {
defer func() {
if err := recover(); err != nil {
diagnostics := getPanicDiagnostics(err)
resp.Diagnostics = diagnostics
}
}()
provisioner, err := t.getProvisioner(req.ProvisionerID)
if err != nil {
return err
}
*resp = req.DeprovisionOutput
provisioner.Deprovision(context.Background(), req.DeprovisionInput, resp)
return nil
}
func (t *RPCServer) getProvisioner(provisionerID proto.ProvisionerID) (sdk.Provisioner, error) {
provisioner, ok := t.provisioners[provisionerID]
if !ok || provisioner == nil {
return nil, &errFunctionFieldNotSet{
objName: provisionerID.String(),
funcName: "Provisioner",
}
}
return provisioner, nil
}
func getPanicDiagnostics(err any) sdk.Diagnostics {
caughtPanic := fmt.Errorf("locally built plugin panicked: %s\nstack trace:\n%s", err, string(debug.Stack()))
return sdk.Diagnostics{Errors: []sdk.Error{{Message: caughtPanic.Error()}}}
}