-
Notifications
You must be signed in to change notification settings - Fork 217
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add ability to wire generated clients to directories
- Loading branch information
Showing
5 changed files
with
716 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package manifestclient | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
|
||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/serializer" | ||
) | ||
|
||
func individualFromList(objList *unstructured.UnstructuredList, name string) (*unstructured.Unstructured, error) { | ||
individualKind := strings.TrimSuffix(objList.GetKind(), "List") | ||
|
||
for _, obj := range objList.Items { | ||
if obj.GetName() != name { | ||
continue | ||
} | ||
|
||
ret := obj.DeepCopy() | ||
ret.SetKind(individualKind) | ||
return ret, nil | ||
} | ||
|
||
return nil, fmt.Errorf("not found in this list") | ||
} | ||
|
||
func readListFile(contentReader RawReader, path string) (*unstructured.UnstructuredList, error) { | ||
content, err := contentReader.ReadFile(path) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to read %q: %w", path, err) | ||
} | ||
|
||
return decodeListObj(content) | ||
} | ||
|
||
func readIndividualFile(contentReader RawReader, path string) (*unstructured.Unstructured, error) { | ||
content, err := contentReader.ReadFile(path) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to read %q: %w", path, err) | ||
} | ||
|
||
return decodeIndividualObj(content) | ||
} | ||
|
||
var localScheme = runtime.NewScheme() | ||
var codecs = serializer.NewCodecFactory(localScheme) | ||
|
||
func decodeIndividualObj(content []byte) (*unstructured.Unstructured, error) { | ||
obj, _, err := codecs.UniversalDecoder().Decode(content, nil, &unstructured.Unstructured{}) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to decode: %w", err) | ||
} | ||
return obj.(*unstructured.Unstructured), nil | ||
} | ||
|
||
func decodeListObj(content []byte) (*unstructured.UnstructuredList, error) { | ||
obj, _, err := codecs.UniversalDecoder().Decode(content, nil, &unstructured.UnstructuredList{}) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to decode: %w", err) | ||
} | ||
return obj.(*unstructured.UnstructuredList), nil | ||
} | ||
|
||
func serializeIndividualObjToJSON(obj *unstructured.Unstructured) (string, error) { | ||
ret, err := json.MarshalIndent(obj.Object, "", " ") | ||
if err != nil { | ||
return "", err | ||
} | ||
return string(ret) + "\n", nil | ||
} | ||
|
||
func serializeListObjToJSON(obj *unstructured.UnstructuredList) (string, error) { | ||
ret, err := json.MarshalIndent(obj, "", " ") | ||
if err != nil { | ||
return "", err | ||
} | ||
return string(ret) + "\n", nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package manifestclient | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io/fs" | ||
"path/filepath" | ||
|
||
apirequest "k8s.io/apiserver/pkg/endpoints/request" | ||
) | ||
|
||
// must-gather has a few different ways to store resources | ||
// 1. cluster-scoped-resource/group/resource/<name>.yaml | ||
// 2. cluster-scoped-resource/group/resource.yaml | ||
// 3. namespaces/<namespace>/group/resource/<name>.yaml | ||
// 4. namespaces/<namespace>/group/resource.yaml | ||
// we have to choose which to prefer and we should always prefer the #2 if it's available. | ||
// Keep in mind that to produce a cluster-scoped list of namespaced resources, you can need to navigate many namespaces. | ||
func (mrt *manifestRoundTripper) get(requestInfo *apirequest.RequestInfo) ([]byte, error) { | ||
if len(requestInfo.Name) == 0 { | ||
return nil, fmt.Errorf("name required for GET") | ||
} | ||
if len(requestInfo.Resource) == 0 { | ||
return nil, fmt.Errorf("resource required for GET") | ||
} | ||
requiredAPIVersion := fmt.Sprintf("%s/%s", requestInfo.APIGroup, requestInfo.APIVersion) | ||
if len(requestInfo.APIGroup) == 0 { | ||
requiredAPIVersion = fmt.Sprintf("%s", requestInfo.APIVersion) | ||
} | ||
|
||
individualFilePath := individualFileLocation(requestInfo) | ||
individualObj, individualErr := readIndividualFile(mrt.contentReader, individualFilePath) | ||
switch { | ||
case errors.Is(individualErr, fs.ErrNotExist): | ||
// try for the list | ||
case individualErr != nil: | ||
return nil, fmt.Errorf("unable to read file: %w", individualErr) | ||
default: | ||
if individualObj.GetAPIVersion() != requiredAPIVersion { | ||
return nil, fmt.Errorf("actual version %v does not match request %v", individualObj.GetAPIVersion(), requiredAPIVersion) | ||
} | ||
ret, err := serializeIndividualObjToJSON(individualObj) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to serialize %v: %v", individualFilePath, err) | ||
} | ||
return []byte(ret), nil | ||
} | ||
|
||
listFilePath := listFileLocation(requestInfo) | ||
listObj, listErr := readListFile(mrt.contentReader, listFilePath) | ||
switch { | ||
case errors.Is(listErr, fs.ErrNotExist): | ||
// we need this to be a not-found when sent back | ||
return nil, newNotFound(requestInfo) | ||
|
||
case listErr != nil: | ||
return nil, fmt.Errorf("unable to read file: %w", listErr) | ||
default: | ||
obj, err := individualFromList(listObj, requestInfo.Name) | ||
if obj == nil { | ||
return nil, newNotFound(requestInfo) | ||
} | ||
if obj.GetAPIVersion() != requiredAPIVersion { | ||
return nil, fmt.Errorf("actual version %v does not match request %v", obj.GetAPIVersion(), requiredAPIVersion) | ||
} | ||
|
||
ret, err := serializeIndividualObjToJSON(obj) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to serialize %v: %v", listFilePath, err) | ||
} | ||
return []byte(ret), nil | ||
} | ||
} | ||
|
||
func individualFileLocation(requestInfo *apirequest.RequestInfo) string { | ||
fileParts := []string{} | ||
|
||
if len(requestInfo.Namespace) > 0 { | ||
fileParts = append(fileParts, "namespaces", requestInfo.Namespace) | ||
} else { | ||
fileParts = append(fileParts, "cluster-scoped-resources") | ||
} | ||
|
||
if len(requestInfo.APIGroup) > 0 { | ||
fileParts = append(fileParts, requestInfo.APIGroup) | ||
} else { | ||
fileParts = append(fileParts, "core") | ||
} | ||
|
||
fileParts = append(fileParts, requestInfo.Resource, fmt.Sprintf("%s.yaml", requestInfo.Name)) | ||
|
||
return filepath.Join(fileParts...) | ||
} | ||
|
||
func listFileLocation(requestInfo *apirequest.RequestInfo) string { | ||
fileParts := []string{} | ||
|
||
if len(requestInfo.Namespace) > 0 { | ||
fileParts = append(fileParts, "namespaces", requestInfo.Namespace) | ||
} else { | ||
fileParts = append(fileParts, "cluster-scoped-resources") | ||
} | ||
|
||
if len(requestInfo.APIGroup) > 0 { | ||
fileParts = append(fileParts, requestInfo.APIGroup) | ||
} else { | ||
fileParts = append(fileParts, "core") | ||
} | ||
|
||
fileParts = append(fileParts, fmt.Sprintf("%s.yaml", requestInfo.Resource)) | ||
|
||
return filepath.Join(fileParts...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package manifestclient | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io/fs" | ||
"path/filepath" | ||
|
||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
apirequest "k8s.io/apiserver/pkg/endpoints/request" | ||
) | ||
|
||
// must-gather has a few different ways to store resources | ||
// 1. cluster-scoped-resource/group/resource/<name>.yaml | ||
// 2. cluster-scoped-resource/group/resource.yaml | ||
// 3. namespaces/<namespace>/group/resource/<name>.yaml | ||
// 4. namespaces/<namespace>/group/resource.yaml | ||
// we have to choose which to prefer and we should always prefer the #2 if it's available. | ||
// Keep in mind that to produce a cluster-scoped list of namespaced resources, you can need to navigate many namespaces. | ||
func (mrt *manifestRoundTripper) list(requestInfo *apirequest.RequestInfo) ([]byte, error) { | ||
var retList *unstructured.UnstructuredList | ||
possibleListFiles, err := allPossibleListFileLocations(mrt.contentReader, requestInfo) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to determine list file locations: %w", err) | ||
} | ||
for _, listFile := range possibleListFiles { | ||
currList, err := readListFile(mrt.contentReader, listFile) | ||
switch { | ||
case errors.Is(err, fs.ErrNotExist): | ||
// do nothing, it's possible, not guaranteed | ||
continue | ||
case err != nil: | ||
return nil, fmt.Errorf("unable to determine read list file %v: %w", listFile, err) | ||
} | ||
|
||
if retList == nil { | ||
retList = currList | ||
continue | ||
} | ||
for i := range currList.Items { | ||
retList.Items = append(retList.Items, currList.Items[i]) | ||
} | ||
} | ||
if retList != nil { | ||
ret, err := serializeListObjToJSON(retList) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to serialize: %v", err) | ||
} | ||
return []byte(ret), nil | ||
} | ||
|
||
retList = &unstructured.UnstructuredList{ | ||
Object: map[string]interface{}{}, | ||
Items: nil, | ||
} | ||
individualFiles, err := allIndividualFileLocations(mrt.contentReader, requestInfo) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to determine individual file locations: %w", err) | ||
} | ||
for _, individualFile := range individualFiles { | ||
currInstance, err := readIndividualFile(mrt.contentReader, individualFile) | ||
switch { | ||
case errors.Is(err, fs.ErrNotExist): | ||
// do nothing, it's possible, not guaranteed | ||
continue | ||
case err != nil: | ||
return nil, fmt.Errorf("unable to determine read list file %v: %w", individualFile, err) | ||
} | ||
|
||
retList.Items = append(retList.Items, *currInstance) | ||
} | ||
if len(retList.Items) > 0 { | ||
retList.SetKind(retList.Items[0].GetKind() + "List") | ||
retList.SetAPIVersion(retList.Items[0].GetAPIVersion()) | ||
|
||
ret, err := serializeListObjToJSON(retList) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to serialize: %v", err) | ||
} | ||
return []byte(ret), nil | ||
} | ||
|
||
return nil, fmt.Errorf("unable to read any file so we have no Kind") | ||
} | ||
|
||
func allIndividualFileLocations(contentReader RawReader, requestInfo *apirequest.RequestInfo) ([]string, error) { | ||
resourceDirectoryParts := []string{} | ||
if len(requestInfo.APIGroup) > 0 { | ||
resourceDirectoryParts = append(resourceDirectoryParts, requestInfo.APIGroup) | ||
} else { | ||
resourceDirectoryParts = append(resourceDirectoryParts, "core") | ||
} | ||
resourceDirectoryParts = append(resourceDirectoryParts, requestInfo.Resource) | ||
|
||
resourceDirectoriesToCheckForIndividualFiles := []string{} | ||
if len(requestInfo.Namespace) > 0 { | ||
parts := append([]string{"namespaces", requestInfo.Namespace}, resourceDirectoryParts...) | ||
resourceDirectoriesToCheckForIndividualFiles = append(resourceDirectoriesToCheckForIndividualFiles, filepath.Join(parts...)) | ||
|
||
} else { | ||
clusterParts := append([]string{"cluster-scoped-resources"}, resourceDirectoryParts...) | ||
resourceDirectoriesToCheckForIndividualFiles = append(resourceDirectoriesToCheckForIndividualFiles, filepath.Join(clusterParts...)) | ||
|
||
namespaces, err := allNamespacesWithData(contentReader) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to read namespaces") | ||
} | ||
for _, ns := range namespaces { | ||
nsParts := append([]string{"namespaces", ns}, resourceDirectoryParts...) | ||
resourceDirectoriesToCheckForIndividualFiles = append(resourceDirectoriesToCheckForIndividualFiles, filepath.Join(nsParts...)) | ||
} | ||
} | ||
|
||
allIndividualFilePaths := []string{} | ||
for _, resourceDirectory := range resourceDirectoriesToCheckForIndividualFiles { | ||
individualFiles, err := contentReader.ReadDir(resourceDirectory) | ||
switch { | ||
case errors.Is(err, fs.ErrNotExist): | ||
continue | ||
case err != nil: | ||
return nil, fmt.Errorf("unable to read resourceDir") | ||
} | ||
|
||
for _, curr := range individualFiles { | ||
allIndividualFilePaths = append(allIndividualFilePaths, filepath.Join(resourceDirectory, curr.Name())) | ||
} | ||
} | ||
|
||
return allIndividualFilePaths, nil | ||
} | ||
|
||
func allPossibleListFileLocations(contentReader RawReader, requestInfo *apirequest.RequestInfo) ([]string, error) { | ||
resourceListFileParts := []string{} | ||
if len(requestInfo.APIGroup) > 0 { | ||
resourceListFileParts = append(resourceListFileParts, requestInfo.APIGroup) | ||
} else { | ||
resourceListFileParts = append(resourceListFileParts, "core") | ||
} | ||
resourceListFileParts = append(resourceListFileParts, fmt.Sprintf("%s.yaml", requestInfo.Resource)) | ||
|
||
allPossibleListFileLocations := []string{} | ||
if len(requestInfo.Namespace) > 0 { | ||
parts := append([]string{"namespaces", requestInfo.Namespace}, resourceListFileParts...) | ||
allPossibleListFileLocations = append(allPossibleListFileLocations, filepath.Join(parts...)) | ||
|
||
} else { | ||
clusterParts := append([]string{"cluster-scoped-resources"}, resourceListFileParts...) | ||
allPossibleListFileLocations = append(allPossibleListFileLocations, filepath.Join(clusterParts...)) | ||
|
||
namespaces, err := allNamespacesWithData(contentReader) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to read namespaces") | ||
} | ||
for _, ns := range namespaces { | ||
nsParts := append([]string{"namespaces", ns}, resourceListFileParts...) | ||
allPossibleListFileLocations = append(allPossibleListFileLocations, filepath.Join(nsParts...)) | ||
} | ||
} | ||
|
||
return allPossibleListFileLocations, nil | ||
} | ||
|
||
func allNamespacesWithData(contentReader RawReader) ([]string, error) { | ||
nsDirs, err := contentReader.ReadDir("namespaces") | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read allNamespacesWithData: %w", err) | ||
} | ||
|
||
ret := []string{} | ||
for _, curr := range nsDirs { | ||
ret = append(ret, curr.Name()) | ||
} | ||
|
||
return ret, nil | ||
} |
Oops, something went wrong.