1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 13:29:41 +02:00

feat(helm): helm apps deployed by portainer not marked as external EE-1624 (#5637)

* helm lib update

* helm handler requires kubernetes deployer to modify helm deployed resources

* AddAppLabels updated to be more generic - support for adding multiple labels using map

* path installed helm release manifest with portainer labels using kubectl

* updated helm handler unit tests to use mock KubernetesDeployer

* adding labels to manifest retrieved from release

* optional namespace support for k8s raw manifest deployment

* - inline postprocessing support when extracting
- get namespace from yaml support
- added and updated tests

* lowercase error wrapping

* updated libhelm dep
This commit is contained in:
zees-dev 2021-09-29 13:12:45 +13:00 committed by GitHub
parent 50f63ae865
commit af98660a55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 494 additions and 30 deletions

View file

@ -11,6 +11,14 @@ import (
"gopkg.in/yaml.v3"
)
const (
labelPortainerAppStackID = "io.portainer.kubernetes.application.stackid"
labelPortainerAppName = "io.portainer.kubernetes.application.name"
labelPortainerAppOwner = "io.portainer.kubernetes.application.owner"
labelPortainerAppKind = "io.portainer.kubernetes.application.kind"
)
// KubeAppLabels are labels applied to all resources deployed in a kubernetes stack
type KubeAppLabels struct {
StackID int
Name string
@ -18,14 +26,49 @@ type KubeAppLabels struct {
Kind string
}
// ToMap converts KubeAppLabels to a map[string]string
func (kal *KubeAppLabels) ToMap() map[string]string {
return map[string]string{
labelPortainerAppStackID: strconv.Itoa(kal.StackID),
labelPortainerAppName: kal.Name,
labelPortainerAppOwner: kal.Owner,
labelPortainerAppKind: kal.Kind,
}
}
// GetHelmAppLabels returns the labels to be applied to portainer deployed helm applications
func GetHelmAppLabels(name, owner string) map[string]string {
return map[string]string{
labelPortainerAppName: name,
labelPortainerAppOwner: owner,
}
}
// AddAppLabels adds required labels to "Resource"->metadata->labels.
// It'll add those labels to all Resource (nodes with a kind property exluding a list) it can find in provided yaml.
// Items in the yaml file could either be organised as a list or broken into multi documents.
func AddAppLabels(manifestYaml []byte, appLabels KubeAppLabels) ([]byte, error) {
func AddAppLabels(manifestYaml []byte, appLabels map[string]string) ([]byte, error) {
if bytes.Equal(manifestYaml, []byte("")) {
return manifestYaml, nil
}
postProcessYaml := func(yamlDoc interface{}) error {
addResourceLabels(yamlDoc, appLabels)
return nil
}
docs, err := ExtractDocuments(manifestYaml, postProcessYaml)
if err != nil {
return nil, err
}
return bytes.Join(docs, []byte("---\n")), nil
}
// ExtractDocuments extracts all the documents from a yaml file
// Optionally post-process each document with a function, which can modify the document in place.
// Pass in nil for postProcessYaml to skip post-processing.
func ExtractDocuments(manifestYaml []byte, postProcessYaml func(interface{}) error) ([][]byte, error) {
docs := make([][]byte, 0)
yamlDecoder := yaml.NewDecoder(bytes.NewReader(manifestYaml))
@ -43,7 +86,12 @@ func AddAppLabels(manifestYaml []byte, appLabels KubeAppLabels) ([]byte, error)
break
}
addResourceLabels(m, appLabels)
// optionally post-process yaml
if postProcessYaml != nil {
if err := postProcessYaml(m); err != nil {
return nil, errors.Wrap(err, "failed to post process yaml document")
}
}
var out bytes.Buffer
yamlEncoder := yaml.NewEncoder(&out)
@ -55,10 +103,29 @@ func AddAppLabels(manifestYaml []byte, appLabels KubeAppLabels) ([]byte, error)
docs = append(docs, out.Bytes())
}
return bytes.Join(docs, []byte("---\n")), nil
return docs, nil
}
func addResourceLabels(yamlDoc interface{}, appLabels KubeAppLabels) {
// GetNamespace returns the namespace of a kubernetes resource from its metadata
// It returns an empty string if namespace is not found in the resource
func GetNamespace(manifestYaml []byte) (string, error) {
yamlDecoder := yaml.NewDecoder(bytes.NewReader(manifestYaml))
m := make(map[string]interface{})
err := yamlDecoder.Decode(&m)
if err != nil {
return "", errors.Wrap(err, "failed to unmarshal yaml manifest when obtaining namespace")
}
if _, ok := m["metadata"]; ok {
if namespace, ok := m["metadata"].(map[string]interface{})["namespace"]; ok {
return namespace.(string), nil
}
}
return "", nil
}
func addResourceLabels(yamlDoc interface{}, appLabels map[string]string) {
m, ok := yamlDoc.(map[string]interface{})
if !ok {
return
@ -82,7 +149,7 @@ func addResourceLabels(yamlDoc interface{}, appLabels KubeAppLabels) {
}
}
func addLabels(obj map[string]interface{}, appLabels KubeAppLabels) {
func addLabels(obj map[string]interface{}, appLabels map[string]string) {
metadata := make(map[string]interface{})
if m, ok := obj["metadata"]; ok {
metadata = m.(map[string]interface{})
@ -95,17 +162,17 @@ func addLabels(obj map[string]interface{}, appLabels KubeAppLabels) {
}
}
name := appLabels.Name
if appLabels.Name == "" {
if n, ok := metadata["name"]; ok {
name = n.(string)
}
// merge app labels with existing labels
for k, v := range appLabels {
labels[k] = v
}
labels["io.portainer.kubernetes.application.stackid"] = strconv.Itoa(appLabels.StackID)
labels["io.portainer.kubernetes.application.name"] = name
labels["io.portainer.kubernetes.application.owner"] = appLabels.Owner
labels["io.portainer.kubernetes.application.kind"] = appLabels.Kind
// fallback to metadata name if name label not explicitly provided
if name, ok := labels[labelPortainerAppName]; !ok || name == "" {
if n, ok := metadata["name"]; ok {
labels[labelPortainerAppName] = n.(string)
}
}
metadata["labels"] = labels
obj["metadata"] = metadata