mirror of https://github.com/fluxcd/cli-utils.git
166 lines
4.9 KiB
Go
166 lines
4.9 KiB
Go
/*
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
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 dispatch
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
|
|
"k8s.io/client-go/kubernetes"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/dynamic"
|
|
"k8s.io/client-go/util/jsonpath"
|
|
"sigs.k8s.io/cli-utils/internal/pkg/apis/dynamic/v1alpha1"
|
|
"sigs.k8s.io/cli-utils/internal/pkg/dy/output"
|
|
"sigs.k8s.io/cli-utils/internal/pkg/dy/parse"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
// Dispatcher dispatches requests for a Command
|
|
type Dispatcher struct {
|
|
// KubernetesClient is used to make requests
|
|
KubernetesClient *kubernetes.Clientset
|
|
|
|
// DynamicClient is used to make requests
|
|
DynamicClient dynamic.Interface
|
|
|
|
// Writer writes templatized output
|
|
Writer *output.CommandOutputWriter
|
|
}
|
|
|
|
// Dispatch sends requests to the apiserver for the Command.
|
|
func (d *Dispatcher) Dispatch(cmd *v1alpha1.ResourceCommand, values *parse.Values) error {
|
|
// Iterate over requests
|
|
for i := range cmd.Requests {
|
|
req := cmd.Requests[i]
|
|
if err := d.do(req, cmd.Command.Use, values); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Dispatcher) do(req v1alpha1.ResourceRequest, name string, values *parse.Values) error {
|
|
// Build the request object
|
|
obj, err := templateToResource(req.BodyTemplate, name+"-resource-request", values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if it is dry-do
|
|
if values.IsDryRun() {
|
|
// Simply print the object rather than making the request
|
|
o, err := yaml.Marshal(obj.Object)
|
|
if err != nil {
|
|
return fmt.Errorf("%v\n%+v", err, obj.Object)
|
|
}
|
|
fmt.Fprintf(d.Writer.Output, "%s---\n", string(o))
|
|
|
|
// Skip making requests for dry-do
|
|
return nil
|
|
}
|
|
|
|
// Send the request
|
|
gvr := schema.GroupVersionResource{Resource: req.Resource, Version: req.Version, Group: req.Group}
|
|
resp, err := d.doRequest(obj, gvr, req.Operation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Save the response values so they can be used when writing the output
|
|
return doResponse(req, resp, values)
|
|
|
|
}
|
|
|
|
// doRequest makes a request to the apiserver with the object (request body), operation (request http operation),
|
|
// group,version,resource (request url).
|
|
func (d *Dispatcher) doRequest(
|
|
obj *unstructured.Unstructured,
|
|
gvr schema.GroupVersionResource,
|
|
op v1alpha1.ResourceOperation) (*unstructured.Unstructured, error) {
|
|
|
|
req := d.DynamicClient.Resource(gvr).Namespace(obj.GetNamespace())
|
|
var resp = &unstructured.Unstructured{}
|
|
var o []byte
|
|
var err error
|
|
|
|
switch op {
|
|
case v1alpha1.PrintResource:
|
|
// Only print the object
|
|
if o, err = yaml.Marshal(obj.Object); err == nil {
|
|
fmt.Fprintf(d.Writer.Output, "%s---\n", string(o))
|
|
}
|
|
case v1alpha1.CreateResource:
|
|
resp, err = req.Create(obj, metav1.CreateOptions{})
|
|
case v1alpha1.DeleteResource:
|
|
err = req.Delete(obj.GetName(), &metav1.DeleteOptions{})
|
|
case v1alpha1.UpdateResource:
|
|
resp, err = req.Update(obj, metav1.UpdateOptions{})
|
|
case v1alpha1.GetResource:
|
|
resp, err = req.Get(obj.GetName(), metav1.GetOptions{})
|
|
case v1alpha1.PatchResource:
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
// templateToResource builds an Unstructured object from a template and flag values
|
|
func templateToResource(t, name string, values *parse.Values) (*unstructured.Unstructured, error) {
|
|
temp, err := template.New(name).Parse(t)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%v\n%s", err, t)
|
|
}
|
|
|
|
body := &bytes.Buffer{}
|
|
if err := temp.Execute(body, values); err != nil {
|
|
return nil, fmt.Errorf("%v\n%s\n%v", err, t, values)
|
|
}
|
|
|
|
// Parse the Resource into an Unstructured object
|
|
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
|
if err := yaml.Unmarshal(body.Bytes(), &obj.Object); err != nil {
|
|
return nil, fmt.Errorf("%v\n%s", err, body.String())
|
|
}
|
|
|
|
return obj, nil
|
|
}
|
|
|
|
// doResponse parses the items specified by JSONPath from the response object back into the Flags struct
|
|
// so that the response is available in the output template
|
|
func doResponse(req v1alpha1.ResourceRequest, resp *unstructured.Unstructured, res *parse.Values) error {
|
|
if res.Responses.Strings == nil {
|
|
res.Responses.Strings = map[string]*string{}
|
|
}
|
|
for i := range req.SaveResponseValues {
|
|
v := req.SaveResponseValues[i]
|
|
j := jsonpath.New(v.Name)
|
|
buf := &bytes.Buffer{}
|
|
if err := j.Parse(v.JSONPath); err != nil {
|
|
return err
|
|
}
|
|
if err := j.Execute(buf, resp.Object); err != nil {
|
|
return err
|
|
}
|
|
s := buf.String()
|
|
res.Responses.Strings[v.Name] = &s
|
|
}
|
|
return nil
|
|
}
|