This repository has been archived by the owner on Jan 13, 2023. It is now read-only.
/
authorize_cluster.go
155 lines (132 loc) · 4.29 KB
/
authorize_cluster.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
/*
Copyright 2020 GramLabs, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package authorize_cluster
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"io"
"text/template"
"github.com/redskyops/redskyops-controller/redskyctl/internal/commander"
"github.com/spf13/cobra"
"github.com/thestormforge/optimize-go/pkg/config"
)
// Options are the configuration options for authorizing a cluster
type Options struct {
GeneratorOptions
}
// NewCommand creates a new command for authorizing a cluster
func NewCommand(o *Options) *cobra.Command {
cmd := &cobra.Command{
Use: "authorize-cluster",
Short: "Authorize a cluster",
Long: "Authorize Red Sky Ops in a cluster",
PreRun: commander.StreamsPreRun(&o.IOStreams),
RunE: commander.WithContextE(o.authorizeCluster),
}
o.addFlags(cmd)
return cmd
}
func (o *Options) authorizeCluster(ctx context.Context) error {
// Fork `kubectl apply` and get a pipe to write manifests to
kubectlApply, err := o.Config.Kubectl(ctx, "apply", "-f", "-")
if err != nil {
return err
}
kubectlApply.Stdout = o.Out
kubectlApply.Stderr = o.ErrOut
w, err := kubectlApply.StdinPipe()
if err != nil {
return err
}
// Generate the secret manifest (populating the name/hash of the secret as a side effect)
var secretName, secretHash string
go func() {
// NOTE: Ignore errors and rely on logging to stderr
defer func() { _ = w.Close() }()
if err := o.generateSecret(w, &secretName, &secretHash); err != nil {
return
}
}()
// Apply the secret manifest (after this returns, the name/hash should be safely populated)
if err := kubectlApply.Run(); err != nil {
return err
}
// Patch the controller deployment using the hash of the secret
if err := o.patchDeployment(ctx, secretName, secretHash); err != nil {
return err
}
return nil
}
// generateSecret produces an authorization configuration secret, as a side effect the name and hash of
// the generated secret are used to populate the supplied string pointers
func (o *Options) generateSecret(out io.Writer, secretName, secretHash *string) error {
h := sha256.New()
opts := o.GeneratorOptions
cmd := NewGeneratorCommand(&opts)
cmd.SetArgs([]string{})
cmd.SetOut(io.MultiWriter(h, out))
cmd.SetErr(o.ErrOut)
if err := cmd.Execute(); err != nil {
return err
}
// Record the name and SHA-256 hash of the secret that was generated
*secretName = opts.Name
*secretHash = hex.EncodeToString(h.Sum(nil))
return nil
}
// patchDeployment patches the Red Sky Controller deployment to reflect the state of the secret; any changes to the
// will cause the controller to be re-deployed.
func (o *Options) patchDeployment(ctx context.Context, secretName, secretHash string) error {
ctrl, err := config.CurrentController(o.Config.Reader())
if err != nil {
return err
}
// Generate the deployment patch using the secret name and hash
var buf bytes.Buffer
tmpl := template.Must(template.New("patch").Parse(patchTemplate))
err = tmpl.Execute(&buf, map[string]string{
"SecretName": secretName,
"SecretHash": secretHash,
})
if err != nil {
return err
}
// Execute the patch
// TODO Deployment name should come from config (it could be different, e.g. for a Helm installation)
name := "redsky-controller-manager"
namespace := ctrl.Namespace
patch := buf.String()
kubectlPatch, err := o.Config.Kubectl(ctx, "patch", "deployment", name, "--namespace", namespace, "--patch", patch)
if err != nil {
return err
}
kubectlPatch.Stdout = o.Out
kubectlPatch.Stderr = o.ErrOut
return kubectlPatch.Run()
}
// patchTemplate is used to patch the deployment with the secret information
const patchTemplate = `
spec:
template:
metadata:
annotations:
"redskyops.dev/secretHash": "{{ .SecretHash }}"
spec:
containers:
- name: manager
envFrom:
- secretRef:
name: "{{ .SecretName }}"
`