Skip to content

Commit

Permalink
[WIP] Improve kubernetes object names
Browse files Browse the repository at this point in the history
Resolves hyperledger-labs#105

Signed-off-by: James Taylor <[email protected]>
  • Loading branch information
jt-nti committed May 1, 2024
1 parent 31ac3e6 commit 7205817
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 56 deletions.
25 changes: 20 additions & 5 deletions cmd/run/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import (
"github.com/hyperledger-labs/fabric-builder-k8s/internal/builder"
"github.com/hyperledger-labs/fabric-builder-k8s/internal/log"
"github.com/hyperledger-labs/fabric-builder-k8s/internal/util"
"k8s.io/apimachinery/pkg/api/validation"
)

const (
expectedArgsLength = 3
buildOutputDirectoryArg = 1
runMetadataDirectoryArg = 2
expectedArgsLength = 3
buildOutputDirectoryArg = 1
runMetadataDirectoryArg = 2
maximumKubeNamePrefixLength = 30
)

func main() {
Expand Down Expand Up @@ -50,20 +52,33 @@ func main() {
if kubeNamespace == "" {
kubeNamespace, err = util.GetKubeNamespace()
if err != nil {
kubeNamespace = "default"
kubeNamespace = util.DefaultNamespace
}
}

kubeServiceAccount := util.GetOptionalEnv(util.ChaincodeServiceAccountVariable, "default")
kubeServiceAccount := util.GetOptionalEnv(util.ChaincodeServiceAccountVariable, util.DefaultServiceAccountName)
logger.Debugf("%s=%s", util.ChaincodeServiceAccountVariable, kubeServiceAccount)

kubeNamePrefix := util.GetOptionalEnv(util.ObjectNamePrefixVariable, util.DefaultObjectNamePrefix)
logger.Debugf("%s=%s", util.ObjectNamePrefixVariable, kubeNamePrefix)

if len(kubeNamePrefix) > maximumKubeNamePrefixLength {
logger.Printf("The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a maximum of 30 characters")
os.Exit(1)
}
if msgs := validation.NameIsDNS1035Label(kubeNamePrefix, true); len(msgs) > 0 {
logger.Printf("The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a valid DNS-1035 label: %s", msgs[0])
os.Exit(1)
}

run := &builder.Run{
BuildOutputDirectory: buildOutputDirectory,
RunMetadataDirectory: runMetadataDirectory,
PeerID: peerID,
KubeconfigPath: kubeconfigPath,
KubeNamespace: kubeNamespace,
KubeServiceAccount: kubeServiceAccount,
KubeNamePrefix: kubeNamePrefix,
}

if err := run.Run(ctx); err != nil {
Expand Down
52 changes: 44 additions & 8 deletions cmd/run/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,60 @@ import (
)

var _ = Describe("Main", func() {
DescribeTable("Running the run command produces the correct error code",
func(expectedErrorCode int, args ...string) {
It("should return an error if the CORE_PEER_ID environment variable is not set", func() {
args := []string{"BUILD_OUTPUT_DIR", "RUN_METADATA_DIR"}
command := exec.Command(runCmdPath, args...)
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())

Eventually(session).Should(gexec.Exit(1))
Eventually(
session.Err,
).Should(gbytes.Say(`run \[\d+\]: Expected CORE_PEER_ID environment variable`))
})

DescribeTable("Running the run command with the wrong arguments produces the correct error",
func(args ...string) {
command := exec.Command(runCmdPath, args...)
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())

Eventually(session).Should(gexec.Exit(expectedErrorCode))
Eventually(session).Should(gexec.Exit(1))
Eventually(
session.Err,
).Should(gbytes.Say(`run \[\d+\]: Expected BUILD_OUTPUT_DIR and RUN_METADATA_DIR arguments`))
},
Entry("When too few arguments are provided", 1, "BUILD_OUTPUT_DIR"),
Entry("When too few arguments are provided", "BUILD_OUTPUT_DIR"),
Entry(
"When too many arguments are provided",
1,
"BUILD_OUTPUT_DIR",
"RUN_METADATA_DIR",
"UNEXPECTED_ARGUMENT",
),
)

DescribeTable("Running the run command produces the correct error for invalid FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable values",
func(kubeNamePrefixValue, expectedErrorMessage string) {
args := []string{"BUILD_OUTPUT_DIR", "RUN_METADATA_DIR"}
command := exec.Command(runCmdPath, args...)
command.Env = append(os.Environ(),
"CORE_PEER_ID=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789",
"FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX="+kubeNamePrefixValue,
)
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())

Eventually(session).Should(gexec.Exit(1))
Eventually(
session.Err,
).Should(gbytes.Say(expectedErrorMessage))
},
Entry("When the FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX is too long", "long-prefix-is-looooooooooooooooooooong", `run \[\d+\]: The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a maximum of 30 characters`),
Entry("When the FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX contains invalid characters", "invalid/PREFIX*", `run \[\d+\]: The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a valid DNS-1035 label: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character`),
Entry("When the FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX starts with a number", "1prefix", `run \[\d+\]: The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a valid DNS-1035 label: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character`),
Entry("When the FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX starts with a dash", "-prefix", `run \[\d+\]: The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a valid DNS-1035 label: a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character`),
)

It(
"should start a chaincode pod using the supplied configuration environment variables",
Label("kind"),
Expand Down Expand Up @@ -59,7 +95,7 @@ var _ = Describe("Main", func() {
).Should(gbytes.Say(`run \[\d+\] DEBUG: FABRIC_K8S_BUILDER_SERVICE_ACCOUNT=chaincode`))
Eventually(
session.Err,
).Should(gbytes.Say(`run \[\d+\]: Running chaincode ID CHAINCODE_ID in kubernetes pod chaincode/cc-m4eml38l6lv2ost7v1i3q6698a5p4gu1l3lnflb4boi7jnle6lt0`))
).Should(gbytes.Say(`run \[\d+\]: Running chaincode ID CHAINCODE_LABEL:CHAINCODE_HASH in kubernetes pod chaincode/hlfcc-chaincodelabel-f15ukm9v906aq`))

pipe := script.Exec(
"kubectl wait --for=condition=ready pod --timeout=120s --namespace=chaincode -l fabric-builder-k8s-peerid=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789",
Expand All @@ -84,8 +120,8 @@ var _ = Describe("Main", func() {
Eventually(descSession.Out).Should(gbytes.Say(`fabric-builder-k8s-mspid=MSPID`))
Eventually(
descSession.Out,
).Should(gbytes.Say(`fabric-builder-k8s-ccid:\s+CHAINCODE_ID`))
Eventually(descSession.Out).Should(gbytes.Say(`CORE_CHAINCODE_ID_NAME:\s+CHAINCODE_ID`))
).Should(gbytes.Say(`fabric-builder-k8s-ccid:\s+CHAINCODE_LABEL:CHAINCODE_HASH`))
Eventually(descSession.Out).Should(gbytes.Say(`CORE_CHAINCODE_ID_NAME:\s+CHAINCODE_LABEL:CHAINCODE_HASH`))
Eventually(descSession.Out).Should(gbytes.Say(`CORE_PEER_ADDRESS:\s+PEER_ADDRESS`))
Eventually(descSession.Out).Should(gbytes.Say(`CORE_PEER_LOCALMSPID:\s+MSPID`))
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/run/testdata/validchaincode/chaincode.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"chaincode_id": "CHAINCODE_ID",
"chaincode_id": "CHAINCODE_LABEL:CHAINCODE_HASH",
"peer_address": "PEER_ADDRESS",
"client_cert": "CLIENT_CERT",
"client_key": "CLIENT_KEY",
Expand Down
5 changes: 5 additions & 0 deletions internal/builder/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Run struct {
KubeconfigPath string
KubeNamespace string
KubeServiceAccount string
KubeNamePrefix string
}

func (r *Run) Run(ctx context.Context) error {
Expand All @@ -33,6 +34,8 @@ func (r *Run) Run(ctx context.Context) error {
return err
}

kubeObjectName := util.GetValidRfc1035LabelName(r.KubeNamePrefix, r.PeerID, chaincodeData)

clientset, err := util.GetKubeClientset(logger, r.KubeconfigPath)
if err != nil {
return fmt.Errorf(
Expand All @@ -48,6 +51,7 @@ func (r *Run) Run(ctx context.Context) error {
ctx,
logger,
secretsClient,
kubeObjectName,
r.KubeNamespace,
r.PeerID,
chaincodeData,
Expand All @@ -66,6 +70,7 @@ func (r *Run) Run(ctx context.Context) error {
ctx,
logger,
podsClient,
kubeObjectName,
r.KubeNamespace,
r.KubeServiceAccount,
r.PeerID,
Expand Down
1 change: 1 addition & 0 deletions internal/util/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
const (
builderVariablePrefix = "FABRIC_K8S_BUILDER_"
ChaincodeNamespaceVariable = builderVariablePrefix + "NAMESPACE"
ObjectNamePrefixVariable = builderVariablePrefix + "OBJECT_NAME_PREFIX"
ChaincodeServiceAccountVariable = builderVariablePrefix + "SERVICE_ACCOUNT"
DebugVariable = builderVariablePrefix + "DEBUG"
KubeconfigPathVariable = "KUBECONFIG_PATH"
Expand Down
21 changes: 21 additions & 0 deletions internal/util/fabric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0

package util

import "strings"

type ChaincodePackageID struct {
Label string
Hash string
}

// NewChaincodePackageID returns a ChaincodePackageID created from the provided string.
func NewChaincodePackageID(chaincodeID string) *ChaincodePackageID {
// TODO add tests!
substrings := strings.Split(chaincodeID, ":")

return &ChaincodePackageID{
Label: strings.Join(substrings[:len(substrings)-1], ":"),
Hash: substrings[len(substrings)-1],
}
}
73 changes: 46 additions & 27 deletions internal/util/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ package util

import (
"context"
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"fmt"
"hash/fnv"
"os"
"regexp"
"strings"
"time"

Expand All @@ -30,6 +31,11 @@ import (
const (
namespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"

// Defaults.
DefaultNamespace string = "default"
DefaultObjectNamePrefix string = "hlfcc"
DefaultServiceAccountName string = "default"

// Mutual TLS auth client key and cert paths in the chaincode container.
TLSClientKeyPath string = "/etc/hyperledger/fabric/client.key"
TLSClientCertPath string = "/etc/hyperledger/fabric/client.crt"
Expand Down Expand Up @@ -302,11 +308,7 @@ func getChaincodePodObject(
Name: "certs",
VolumeSource: apiv1.VolumeSource{
Secret: &apiv1.SecretVolumeSource{
SecretName: GetValidName(
chaincodeData.MspID,
peerID,
chaincodeData.ChaincodeID,
),
SecretName: podName,
},
},
},
Expand All @@ -316,11 +318,9 @@ func getChaincodePodObject(
}

func getChaincodeSecretApplyConfiguration(
namespace, peerID string,
secretName, namespace, peerID string,
chaincodeData *ChaincodeJSON,
) *applycorev1.SecretApplyConfiguration {
name := GetValidName(chaincodeData.MspID, peerID, chaincodeData.ChaincodeID)

annotations := map[string]string{
"fabric-builder-k8s-ccid": chaincodeData.ChaincodeID,
}
Expand All @@ -343,7 +343,7 @@ func getChaincodeSecretApplyConfiguration(
}

return applycorev1.
Secret(name, namespace).
Secret(secretName, namespace).
WithAnnotations(annotations).
WithLabels(labels).
WithStringData(data).
Expand All @@ -354,10 +354,10 @@ func ApplyChaincodeSecrets(
ctx context.Context,
logger *log.CmdLogger,
secretsClient v1.SecretInterface,
namespace, peerID string,
secretName, namespace, peerID string,
chaincodeData *ChaincodeJSON,
) error {
secret := getChaincodeSecretApplyConfiguration(namespace, peerID, chaincodeData)
secret := getChaincodeSecretApplyConfiguration(secretName, namespace, peerID, chaincodeData)

result, err := secretsClient.Apply(
ctx,
Expand Down Expand Up @@ -434,26 +434,25 @@ func CreateChaincodePod(
ctx context.Context,
logger *log.CmdLogger,
podsClient v1.PodInterface,
namespace, serviceAccount, peerID string,
objectName, namespace, serviceAccount, peerID string,
chaincodeData *ChaincodeJSON,
imageData *ImageJSON,
) (*apiv1.Pod, error) {
podName := GetValidName(chaincodeData.MspID, peerID, chaincodeData.ChaincodeID)
podDefinition := getChaincodePodObject(
imageData,
namespace,
serviceAccount,
podName,
objectName,
peerID,
chaincodeData,
)

err := deleteChaincodePod(ctx, logger, podsClient, podName, namespace, chaincodeData)
err := deleteChaincodePod(ctx, logger, podsClient, objectName, namespace, chaincodeData)
if err != nil {
return nil, fmt.Errorf(
"unable to delete existing chaincode pod %s/%s for chaincode ID %s: %w",
namespace,
podName,
objectName,
chaincodeData.ChaincodeID,
err,
)
Expand All @@ -463,15 +462,15 @@ func CreateChaincodePod(
"Creating chaincode pod for chaincode ID %s: %s/%s",
chaincodeData.ChaincodeID,
namespace,
podName,
objectName,
)

pod, err := podsClient.Create(ctx, podDefinition, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf(
"unable to create chaincode pod %s/%s for chaincode ID %s: %w",
namespace,
podName,
objectName,
chaincodeData.ChaincodeID,
err,
)
Expand All @@ -487,13 +486,33 @@ func CreateChaincodePod(
return pod, nil
}

// GetValidName returns a valid RFC 1035 label name.
func GetValidName(mspID, peerID, chaincodeID string) string {
qualifiedChaincodeID := mspID + ":" + peerID + ":" + chaincodeID
h := sha256.New()
h.Write([]byte(qualifiedChaincodeID))
sum := h.Sum(nil)
encodedChaincodeID := base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(sum)
// GetValidRfc1035LabelName returns a valid RFC 1035 label name with the format
// <prefix>-<truncated_chaincode_label>-<chaincode_run_hash>.
func GetValidRfc1035LabelName(prefix, peerID string, chaincodeData *ChaincodeJSON) string {
const (
maxRfc1035LabelLength = 63
labelSeparators = 2
)

runHash := fnv.New64a()
runHash.Write([]byte(prefix))
runHash.Write([]byte(peerID))
runHash.Write([]byte(chaincodeData.PeerAddress))
runHash.Write([]byte(chaincodeData.MspID))
runHash.Write([]byte(chaincodeData.ChaincodeID))
suffix := strings.ToLower(base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(runHash.Sum(nil)))

// Remove unsafe characters from the chaincode package label
packageID := NewChaincodePackageID(chaincodeData.ChaincodeID)
re := regexp.MustCompile("[^-0-9a-z]")
safeLabel := re.ReplaceAllString(strings.ToLower(packageID.Label), "")

// Make sure the chaincode package label fits in the space available,
// taking in to account the prefix, suffix, and two '-' separators
maxLabelLength := maxRfc1035LabelLength - len(prefix) - len(suffix) - labelSeparators
if maxLabelLength < len(safeLabel) {
safeLabel = safeLabel[:maxLabelLength]
}

return "cc-" + strings.ToLower(encodedChaincodeID)
return prefix + "-" + safeLabel + "-" + suffix
}
Loading

0 comments on commit 7205817

Please sign in to comment.