Skip to content

Commit 8d3fbda

Browse files
committed
cli: create run servicedirectory command
This commit enables the operator to run with Google Service Directory. This command is not enabled yet, and will be once all others will be completed as well. Signed-off-by: Elis Lulja <[email protected]>
1 parent 1f4929b commit 8d3fbda

File tree

2 files changed

+270
-1
lines changed

2 files changed

+270
-1
lines changed

pkg/command/run/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ func GetRunCommand() *cobra.Command {
324324
// Sub commands
325325
// -----------------------------
326326

327-
// TODO: add commands
327+
cmd.AddCommand(getRunServiceDirectoryCommand(opts))
328328

329329
return cmd
330330
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
// Copyright © 2022 Cisco
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//
17+
// All rights reserved.
18+
19+
package run
20+
21+
import (
22+
"context"
23+
"fmt"
24+
"io/ioutil"
25+
"time"
26+
27+
servicedirectory "cloud.google.com/go/servicedirectory/apiv1"
28+
"github.com/CloudNativeSDWAN/cnwan-operator/pkg/cluster"
29+
sd "github.com/CloudNativeSDWAN/cnwan-operator/pkg/servregistry/gcloud/servicedirectory"
30+
"github.com/spf13/cobra"
31+
"google.golang.org/api/option"
32+
"gopkg.in/yaml.v3"
33+
ctrl "sigs.k8s.io/controller-runtime"
34+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
35+
)
36+
37+
const (
38+
defaultServiceDirectoryConfigMapName = "service-directory-options"
39+
defaultServiceDirectoryServiceAccountSecretName = "service-directory-service-account"
40+
autoKey = "auto"
41+
)
42+
43+
type ServiceDirectoryOptions struct {
44+
DefaultRegion string `yaml:"defaultRegion"`
45+
ProjectID string `yaml:"projectID"`
46+
47+
serviceAccountBytes []byte
48+
}
49+
50+
func getRunServiceDirectoryCommand(operatorOpts *Options) *cobra.Command {
51+
// -----------------------------
52+
// Inits and defaults
53+
// -----------------------------
54+
55+
opts := &ServiceDirectoryOptions{}
56+
var (
57+
optsPath string
58+
optsConfigMap string
59+
servAccPath string
60+
servAccSecret string
61+
)
62+
63+
// -----------------------------
64+
// The command
65+
// -----------------------------
66+
67+
cmd := &cobra.Command{
68+
Use: "servicedirectory [COMMAND] [OPTIONS]",
69+
Aliases: []string{"sd", "google-service-directory", "with-service-directory"},
70+
Short: "Run the program with Google Service Directory",
71+
PreRunE: func(cmd *cobra.Command, args []string) error {
72+
l := log.With().Str("cmd", "service-directory").Logger()
73+
74+
// -- Get the options from file or ConfigMap
75+
if optsPath != "" || optsConfigMap != "" {
76+
var (
77+
fileOptions []byte
78+
decodedFileOptions *ServiceDirectoryOptions
79+
)
80+
81+
// -- Get the options from path
82+
if optsPath != "" {
83+
if optsConfigMap != "" {
84+
l.Warn().Msg("both path and configmap flags are provided: only the path will be used")
85+
optsConfigMap = ""
86+
}
87+
88+
log.Debug().Str("path", optsPath).
89+
Msg("getting options from file...")
90+
byteOpts, err := ioutil.ReadFile(optsPath)
91+
if err != nil {
92+
return fmt.Errorf("cannot open file %s: %w", optsPath, err)
93+
}
94+
95+
fileOptions = byteOpts
96+
}
97+
98+
// -- Get options from configmap
99+
if optsConfigMap != "" {
100+
log.Debug().
101+
Str("namespace", operatorOpts.Namespace).
102+
Str("name", optsConfigMap).
103+
Msg("getting options from configmap...")
104+
105+
ctx, canc := context.WithTimeout(context.Background(), 10*time.Second)
106+
cfg, err := cluster.GetFilesFromConfigMap(ctx, operatorOpts.Namespace, optsConfigMap)
107+
if err != nil {
108+
canc()
109+
return fmt.Errorf("cannot get configmap: %w", err)
110+
}
111+
canc()
112+
113+
fileOptions = cfg[0]
114+
}
115+
116+
if len(fileOptions) > 0 {
117+
if err := yaml.Unmarshal(fileOptions, &decodedFileOptions); err != nil {
118+
return fmt.Errorf("cannot decode options %s: %w", optsPath, err)
119+
}
120+
}
121+
122+
// -- Parse the cmd flags
123+
if !cmd.Flag("default-region").Changed {
124+
opts.DefaultRegion = decodedFileOptions.DefaultRegion
125+
}
126+
127+
if !cmd.Flag("project-id").Changed {
128+
opts.ProjectID = decodedFileOptions.ProjectID
129+
}
130+
}
131+
132+
// -- Are the settings provided at least?
133+
if opts.DefaultRegion == "" {
134+
return fmt.Errorf("no region provided")
135+
}
136+
137+
if opts.ProjectID == "" {
138+
return fmt.Errorf("no project ID provided")
139+
}
140+
141+
// -- Get data automatically?
142+
if opts.DefaultRegion == autoKey || opts.ProjectID == autoKey {
143+
if cluster.WhereAmIRunning() != cluster.GKECluster {
144+
return fmt.Errorf("cannot get data automatically: cluster is not GKE")
145+
}
146+
147+
defaultRegion, err := cluster.GetGCPRegion()
148+
if err != nil {
149+
return fmt.Errorf("could not get region from GCP: %w", err)
150+
}
151+
opts.DefaultRegion = *defaultRegion
152+
l.Info().Str("region", opts.DefaultRegion).Msg("retrieved region from GKE")
153+
154+
projectID, err := cluster.GetGCPProjectID()
155+
if err != nil {
156+
return fmt.Errorf("could not get project ID from GCP: %w", err)
157+
}
158+
opts.ProjectID = *projectID
159+
l.Info().Str("project-id", opts.ProjectID).Msg("retrieved project ID from GCP")
160+
}
161+
162+
// -- Get the service account
163+
if servAccPath == "" && servAccSecret == "" {
164+
return fmt.Errorf("no service account provided")
165+
}
166+
167+
if servAccPath != "" {
168+
if servAccSecret != "" {
169+
l.Warn().Msg("both path and secret flags are provided: only the path will be used")
170+
servAccSecret = ""
171+
}
172+
173+
l.Debug().Str("path", servAccPath).
174+
Msg("getting service account from file...")
175+
byteOpts, err := ioutil.ReadFile(servAccPath)
176+
if err != nil {
177+
return fmt.Errorf("cannot open file %s: %w", servAccPath, err)
178+
}
179+
180+
opts.serviceAccountBytes = byteOpts
181+
}
182+
183+
if servAccSecret != "" {
184+
l.Debug().
185+
Str("namespace", operatorOpts.Namespace).
186+
Str("name", servAccSecret).
187+
Msg("getting service account from secret...")
188+
189+
ctx, canc := context.WithTimeout(context.Background(), 10*time.Second)
190+
secret, err := cluster.GetFilesFromSecret(ctx, operatorOpts.Namespace, servAccSecret)
191+
if err != nil {
192+
canc()
193+
return fmt.Errorf("cannot get secret: %w", err)
194+
}
195+
canc()
196+
197+
opts.serviceAccountBytes = secret[0]
198+
}
199+
200+
return nil
201+
},
202+
RunE: func(_ *cobra.Command, _ []string) error {
203+
return runWithServiceDirectory(operatorOpts, opts)
204+
},
205+
Example: "servicedirectory --project-id my-project-id default-region us-east1",
206+
}
207+
208+
// -----------------------------
209+
// Flags
210+
// -----------------------------
211+
212+
cmd.Flags().StringVar(&opts.DefaultRegion, "default-region", "",
213+
"region/location where to register resources. Write auto to try to get it automatically.")
214+
cmd.Flags().StringVar(&opts.ProjectID, "project-id", "",
215+
"Google Cloud project ID. Write auto to try to get it automatically.")
216+
cmd.Flags().StringVar(&optsPath, "options-path", "",
217+
"path to the file containing service directory options.")
218+
cmd.Flags().StringVar(&optsConfigMap, "options-configmap", func() string {
219+
if operatorOpts.RunningInK8s {
220+
return defaultServiceDirectoryConfigMapName
221+
}
222+
223+
return ""
224+
}(),
225+
"name of the Kubernetes config map containing settings.")
226+
cmd.Flags().StringVar(&servAccPath, "service-account-path", "",
227+
"path to the service account file.")
228+
cmd.Flags().StringVar(&servAccSecret, "service-account-secret", func() string {
229+
if operatorOpts.RunningInK8s {
230+
return defaultServiceDirectoryServiceAccountSecretName
231+
}
232+
233+
return ""
234+
}(),
235+
"name of the Kubernetes secret containing the service account.")
236+
237+
return cmd
238+
}
239+
240+
func runWithServiceDirectory(operatorOpts *Options, sdOpts *ServiceDirectoryOptions) error {
241+
ctx, canc := context.WithTimeout(context.Background(), 15*time.Second)
242+
defer canc()
243+
244+
cli, err := servicedirectory.
245+
NewRegistrationClient(ctx, option.WithCredentialsJSON(sdOpts.serviceAccountBytes))
246+
if err != nil {
247+
return fmt.Errorf("could not get start service directory client: %s", err)
248+
}
249+
250+
defer cli.Close()
251+
252+
// TODO: when #81 is solved an merged this will be replaced
253+
// with zerolog
254+
l := ctrl.Log.WithName("ServiceDirectory")
255+
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
256+
257+
sr := &sd.Handler{
258+
ProjectID: sdOpts.ProjectID,
259+
DefaultRegion: sdOpts.DefaultRegion,
260+
Log: l,
261+
Context: ctx,
262+
Client: cli,
263+
}
264+
265+
// TODO: use the handler (in next commits)
266+
_ = sr
267+
268+
return nil
269+
}

0 commit comments

Comments
 (0)