Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract custom default values from package tarball #5702

Merged
merged 5 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions cmd/kubeapps-apis/docs/kubeapps-apis.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4437,14 +4437,6 @@
],
"default": "PACKAGE_REPOSITORY_AUTH_TYPE_UNSPECIFIED"
},
"pluginsfluxv2packagesv1alpha1SetUserManagedSecretsResponse": {
"type": "object",
"properties": {
"value": {
"type": "boolean"
}
}
},
"protobufAny": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -4582,6 +4574,14 @@
"description": "An example of default values used during package templating that can serve\nas documentation or a starting point for user customization.",
"title": "Available package default values"
},
"additionalDefaultValues": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "A package may contain additional default value files for specific scenarios,\nsuch as values_production.yaml or values_dev.yaml",
"title": "Available package additional default values"
},
"valuesSchema": {
"type": "string"
},
Expand Down Expand Up @@ -5565,6 +5565,14 @@
"description": "The type of secret. Currently Kubeapps itself only deals with OPAQUE\nand docker config json secrets, but we define all so we can correctly\nlist the secret names with their types.\nSee https://kubernetes.io/docs/concepts/configuration/secret/#secret-types",
"title": "SecretType"
},
"v1alpha1SetUserManagedSecretsResponse": {
"type": "object",
"properties": {
"value": {
"type": "boolean"
}
}
},
"v1alpha1SshCredentials": {
"type": "object",
"properties": {
Expand Down
904 changes: 466 additions & 438 deletions cmd/kubeapps-apis/gen/core/packages/v1alpha1/packages.pb.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,12 @@ message AvailablePackageDetail {
// as documentation or a starting point for user customization.
string default_values = 11;

// Available package additional default values
//
// A package may contain additional default value files for specific scenarios,
// such as values_production.yaml or values_dev.yaml
map<string, string> additional_default_values = 17;

// Available package values schema
//
// An optional openapi/json schema that can be used to validate a user-provided values.
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/actions/availablepackages.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const defaultAvailablePackageDetail: AvailablePackageDetail = {
},
valuesSchema: "",
defaultValues: "",
additionalDefaultValues: {},
maintainers: [],
readme: "",
version: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const testProps: IPackageHeaderProps = {
},
valuesSchema: "",
defaultValues: "",
additionalDefaultValues: {},
maintainers: [],
readme: "",
version: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const defaultAvailablePkgDetail: AvailablePackageDetail = {
},
valuesSchema: "test",
defaultValues: "test",
additionalDefaultValues: {},
maintainers: [{ name: "test", email: "test" }] as Maintainer[],
readme: "test",
version: {
Expand Down
121 changes: 121 additions & 0 deletions dashboard/src/gen/kubeappsapis/core/packages/v1alpha1/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,13 @@ export interface AvailablePackageDetail {
* as documentation or a starting point for user customization.
*/
defaultValues: string;
/**
* Available package additional default values
*
* A package may contain additional default value files for specific scenarios,
* such as values_production.yaml or values_dev.yaml
*/
additionalDefaultValues: { [key: string]: string };
valuesSchema: string;
/** source urls for the package */
sourceUrls: string[];
Expand Down Expand Up @@ -468,6 +475,11 @@ export interface AvailablePackageDetail {
customDetail?: Any;
}

export interface AvailablePackageDetail_AdditionalDefaultValuesEntry {
key: string;
value: string;
}

/**
* InstalledPackageSummary
*
Expand Down Expand Up @@ -2518,6 +2530,7 @@ function createBaseAvailablePackageDetail(): AvailablePackageDetail {
longDescription: "",
readme: "",
defaultValues: "",
additionalDefaultValues: {},
valuesSchema: "",
sourceUrls: [],
maintainers: [],
Expand Down Expand Up @@ -2564,6 +2577,12 @@ export const AvailablePackageDetail = {
if (message.defaultValues !== "") {
writer.uint32(90).string(message.defaultValues);
}
Object.entries(message.additionalDefaultValues).forEach(([key, value]) => {
AvailablePackageDetail_AdditionalDefaultValuesEntry.encode(
{ key: key as any, value },
writer.uint32(138).fork(),
).ldelim();
});
if (message.valuesSchema !== "") {
writer.uint32(98).string(message.valuesSchema);
}
Expand Down Expand Up @@ -2622,6 +2641,15 @@ export const AvailablePackageDetail = {
case 11:
message.defaultValues = reader.string();
break;
case 17:
const entry17 = AvailablePackageDetail_AdditionalDefaultValuesEntry.decode(
reader,
reader.uint32(),
);
if (entry17.value !== undefined) {
message.additionalDefaultValues[entry17.key] = entry17.value;
}
break;
case 12:
message.valuesSchema = reader.string();
break;
Expand Down Expand Up @@ -2660,6 +2688,15 @@ export const AvailablePackageDetail = {
longDescription: isSet(object.longDescription) ? String(object.longDescription) : "",
readme: isSet(object.readme) ? String(object.readme) : "",
defaultValues: isSet(object.defaultValues) ? String(object.defaultValues) : "",
additionalDefaultValues: isObject(object.additionalDefaultValues)
? Object.entries(object.additionalDefaultValues).reduce<{ [key: string]: string }>(
(acc, [key, value]) => {
acc[key] = String(value);
return acc;
},
{},
)
: {},
valuesSchema: isSet(object.valuesSchema) ? String(object.valuesSchema) : "",
sourceUrls: Array.isArray(object?.sourceUrls)
? object.sourceUrls.map((e: any) => String(e))
Expand Down Expand Up @@ -2691,6 +2728,12 @@ export const AvailablePackageDetail = {
message.longDescription !== undefined && (obj.longDescription = message.longDescription);
message.readme !== undefined && (obj.readme = message.readme);
message.defaultValues !== undefined && (obj.defaultValues = message.defaultValues);
obj.additionalDefaultValues = {};
if (message.additionalDefaultValues) {
Object.entries(message.additionalDefaultValues).forEach(([k, v]) => {
obj.additionalDefaultValues[k] = v;
});
}
message.valuesSchema !== undefined && (obj.valuesSchema = message.valuesSchema);
if (message.sourceUrls) {
obj.sourceUrls = message.sourceUrls.map(e => e);
Expand Down Expand Up @@ -2733,6 +2776,14 @@ export const AvailablePackageDetail = {
message.longDescription = object.longDescription ?? "";
message.readme = object.readme ?? "";
message.defaultValues = object.defaultValues ?? "";
message.additionalDefaultValues = Object.entries(object.additionalDefaultValues ?? {}).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
if (value !== undefined) {
acc[key] = String(value);
}
return acc;
}, {});
message.valuesSchema = object.valuesSchema ?? "";
message.sourceUrls = object.sourceUrls?.map(e => e) || [];
message.maintainers = object.maintainers?.map(e => Maintainer.fromPartial(e)) || [];
Expand All @@ -2745,6 +2796,72 @@ export const AvailablePackageDetail = {
},
};

function createBaseAvailablePackageDetail_AdditionalDefaultValuesEntry(): AvailablePackageDetail_AdditionalDefaultValuesEntry {
return { key: "", value: "" };
}

export const AvailablePackageDetail_AdditionalDefaultValuesEntry = {
encode(
message: AvailablePackageDetail_AdditionalDefaultValuesEntry,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.key !== "") {
writer.uint32(10).string(message.key);
}
if (message.value !== "") {
writer.uint32(18).string(message.value);
}
return writer;
},

decode(
input: _m0.Reader | Uint8Array,
length?: number,
): AvailablePackageDetail_AdditionalDefaultValuesEntry {
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseAvailablePackageDetail_AdditionalDefaultValuesEntry();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
message.key = reader.string();
break;
case 2:
message.value = reader.string();
break;
default:
reader.skipType(tag & 7);
break;
}
}
return message;
},

fromJSON(object: any): AvailablePackageDetail_AdditionalDefaultValuesEntry {
return {
key: isSet(object.key) ? String(object.key) : "",
value: isSet(object.value) ? String(object.value) : "",
};
},

toJSON(message: AvailablePackageDetail_AdditionalDefaultValuesEntry): unknown {
const obj: any = {};
message.key !== undefined && (obj.key = message.key);
message.value !== undefined && (obj.value = message.value);
return obj;
},

fromPartial<I extends Exact<DeepPartial<AvailablePackageDetail_AdditionalDefaultValuesEntry>, I>>(
object: I,
): AvailablePackageDetail_AdditionalDefaultValuesEntry {
const message = createBaseAvailablePackageDetail_AdditionalDefaultValuesEntry();
message.key = object.key ?? "";
message.value = object.value ?? "";
return message;
},
};

function createBaseInstalledPackageSummary(): InstalledPackageSummary {
return {
installedPackageRef: undefined,
Expand Down Expand Up @@ -4368,6 +4485,10 @@ export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };

function isObject(value: any): boolean {
return typeof value === "object" && value !== null;
}

function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
Expand Down
21 changes: 12 additions & 9 deletions pkg/chart/models/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@ type ChartVersion struct {
Schema string `json:"schema" bson:"-"`
}

// ChartFiles holds the README and values for a given chart version
// ChartFiles holds the README and default values for a given chart version
type ChartFiles struct {
ID string `bson:"file_id"`
Readme string
Values string
Schema string
Repo *Repo
Digest string
// TODO(absoludity): Rename to DefaultValues in separate PR for easy review.
Values string
CustomDefaultValues map[string]string
Schema string
Repo *Repo
Digest string
}

// Allow to convert ChartFiles to a sql JSON
Expand All @@ -92,8 +94,9 @@ func (a ChartFiles) Value() (driver.Value, error) {

// some constant strings used as keys in maps in several modules
const (
ReadmeKey = "readme"
ValuesKey = "values"
SchemaKey = "schema"
ChartYamlKey = "chartYaml"
ReadmeKey = "readme"
ValuesKey = "values"
AdditionalDefaultValuesKey = "additionalDefaultValues"
SchemaKey = "schema"
ChartYamlKey = "chartYaml"
)
57 changes: 43 additions & 14 deletions pkg/tarutil/tarutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"io"
"net/url"
"path"
"regexp"
"strings"

chart "github.com/vmware-tanzu/kubeapps/pkg/chart/models"
Expand Down Expand Up @@ -80,20 +81,22 @@ func FetchChartDetailFromTarball(reader io.Reader, name string) (map[string]stri
chart.ChartYamlKey: chartYamlFileName,
}

files, err := ExtractFilesFromTarball(filenames, tarf)
if err != nil {
return nil, err
// Optionally search for files matching a regular expression, using the
// template to provide the key.
regexes := map[string]*regexp.Regexp{
chart.ValuesKey + "-$valuesType": regexp.MustCompile(fixedName + `/values-(?P<valuesType>\w+)\.yaml`),
}

return map[string]string{
chart.ValuesKey: files[chart.ValuesKey],
chart.ReadmeKey: files[chart.ReadmeKey],
chart.SchemaKey: files[chart.SchemaKey],
chart.ChartYamlKey: files[chart.ChartYamlKey],
}, nil
return ExtractFilesFromTarball(filenames, regexes, tarf)
}

func ExtractFilesFromTarball(filenames map[string]string, tarf *tar.Reader) (map[string]string, error) {
// ExtractFilesFromTarball returns the content of extracted files in a map.
//
// Files can be extracted by exact matches on the filename, or by regular
// expression matches. For exact matches, the key used in the resulting map
// is simply the key of the filename. For regex matches, a regexp template
// defines the key so that it can be expanded from the match.
func ExtractFilesFromTarball(filenames map[string]string, regexes map[string]*regexp.Regexp, tarf *tar.Reader) (map[string]string, error) {
ret := make(map[string]string)
for {
header, err := tarf.Next()
Expand All @@ -104,17 +107,43 @@ func ExtractFilesFromTarball(filenames map[string]string, tarf *tar.Reader) (map
return ret, err
}

foundFile := false
for id, f := range filenames {
if strings.EqualFold(header.Name, f) {
var b bytes.Buffer
_, err := io.Copy(&b, tarf)
if err != nil {
if s, err := readTarFileContent(tarf); err != nil {
return ret, err
} else {
ret[id] = s
}
ret[id] = b.String()
foundFile = true
break
}
}
if foundFile {
continue
}

for template, pattern := range regexes {
match := pattern.FindSubmatchIndex([]byte(header.Name))
if match != nil {
result := []byte{}
result = pattern.ExpandString(result, template, header.Name, match)
if s, err := readTarFileContent(tarf); err != nil {
return ret, err
} else {
ret[string(result)] = s
}
}
}
}
return ret, nil
}

func readTarFileContent(tarf *tar.Reader) (string, error) {
var b bytes.Buffer
_, err := io.Copy(&b, tarf)
if err != nil {
return "", err
}
return b.String(), nil
}
Loading