diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 30814f3..9d8f071 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -44,6 +44,7 @@ jobs: run: go test -v ./... env: FABRIC_K8S_BUILDER_DEBUG: 'true' + INCLUDE_KIND_TESTS: ${{ matrix.os == 'ubuntu-latest' && 'true' || 'false' }} - name: Package run: | diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 53fc901..f087ecc 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -28,6 +28,6 @@ jobs: go-version: 1.17 - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@537aa1903e5d359d0b27dbc19ddd22c5087f3fbc + uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 with: - version: v1.46.2 + version: v1.50.1 diff --git a/.golangci.yml b/.golangci.yml index 012ed25..c2c4059 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,7 +9,6 @@ linters: - containedctx - contextcheck - cyclop - - deadcode - decorder - depguard - dogsled @@ -45,11 +44,9 @@ linters: - gosimple - govet - grouper - - ifshort - importas - ineffassign - ireturn - - lll - maintidx - makezero - misspell @@ -69,7 +66,6 @@ linters: - rowserrcheck - sqlclosecheck - staticcheck - - structcheck - stylecheck - tenv - testpackage @@ -79,7 +75,6 @@ linters: - unconvert - unparam - unused - - varcheck - varnamelen - wastedassign - whitespace @@ -87,6 +82,8 @@ linters: linters-settings: errorlint: errorf: true + funlen: + lines: 100 nolintlint: require-explanation: true require-specific: true diff --git a/README.md b/README.md index cf1b87a..7e78052 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,11 @@ With the k8s-builder, _chaincode just works!_ The k8s builder can be run in cluster using the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment variables, or it can connect using a `KUBECONFIG_PATH` environment variable. -An optional `FABRIC_K8S_BUILDER_NAMESPACE` can be used to specify the namespace to deploy chaincode to. +The following optional environment variables can be used to configure the k8s builder: + +- `FABRIC_K8S_BUILDER_DEBUG` whether to enable additional logging +- `FABRIC_K8S_BUILDER_NAMESPACE` specifies the namespace to deploy chaincode to +- `FABRIC_K8S_BUILDER_SERVICE_ACCOUNT` specifies the service account for the chaincode pod A `CORE_PEER_ID` environment variable is also currently required. @@ -64,6 +68,7 @@ External builders are configured in the `core.yaml` file, for example: - CORE_PEER_ID - FABRIC_K8S_BUILDER_DEBUG - FABRIC_K8S_BUILDER_NAMESPACE + - FABRIC_K8S_BUILDER_SERVICE_ACCOUNT - KUBERNETES_SERVICE_HOST - KUBERNETES_SERVICE_PORT ``` diff --git a/cmd/build/build_suite_test.go b/cmd/build/build_suite_test.go index 52e4118..74ba4e3 100644 --- a/cmd/build/build_suite_test.go +++ b/cmd/build/build_suite_test.go @@ -2,6 +2,7 @@ package main_test import ( "testing" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -17,6 +18,8 @@ func TestBuild(t *testing.T) { } var _ = BeforeSuite(func() { + SetDefaultEventuallyTimeout(2 * time.Second) + var err error buildCmdPath, err = gexec.Build("github.com/hyperledger-labs/fabric-builder-k8s/cmd/build") Expect(err).NotTo(HaveOccurred()) diff --git a/cmd/build/main.go b/cmd/build/main.go index 846f7e5..6cb8c59 100644 --- a/cmd/build/main.go +++ b/cmd/build/main.go @@ -24,7 +24,9 @@ func main() { logger := log.New(ctx) if len(os.Args) != expectedArgsLength { - logger.Println("Expected CHAINCODE_SOURCE_DIR, CHAINCODE_METADATA_DIR and BUILD_OUTPUT_DIR arguments") + logger.Println( + "Expected CHAINCODE_SOURCE_DIR, CHAINCODE_METADATA_DIR and BUILD_OUTPUT_DIR arguments", + ) os.Exit(1) } diff --git a/cmd/build/main_test.go b/cmd/build/main_test.go index fc8188a..7b820f0 100644 --- a/cmd/build/main_test.go +++ b/cmd/build/main_test.go @@ -37,7 +37,12 @@ var _ = Describe("Main", func() { return []string{"CHAINCODE_SOURCE_DIR"} }), Entry("When too many arguments are provided", 1, func() []string { - return []string{"CHAINCODE_SOURCE_DIR", "CHAINCODE_METADATA_DIR", "BUILD_OUTPUT_DIR", "UNEXPECTED_ARGUMENT"} + return []string{ + "CHAINCODE_SOURCE_DIR", + "CHAINCODE_METADATA_DIR", + "BUILD_OUTPUT_DIR", + "UNEXPECTED_ARGUMENT", + } }), ) diff --git a/cmd/detect/detect_suite_test.go b/cmd/detect/detect_suite_test.go index 3aaa9a9..8d48505 100644 --- a/cmd/detect/detect_suite_test.go +++ b/cmd/detect/detect_suite_test.go @@ -2,6 +2,7 @@ package main_test import ( "testing" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -17,6 +18,8 @@ func TestDetect(t *testing.T) { } var _ = BeforeSuite(func() { + SetDefaultEventuallyTimeout(2 * time.Second) + var err error detectCmdPath, err = gexec.Build("github.com/hyperledger-labs/fabric-builder-k8s/cmd/detect") Expect(err).NotTo(HaveOccurred()) diff --git a/cmd/detect/main_test.go b/cmd/detect/main_test.go index c5d5ec9..05d7cdb 100644 --- a/cmd/detect/main_test.go +++ b/cmd/detect/main_test.go @@ -10,7 +10,8 @@ import ( ) var _ = Describe("Main", func() { - DescribeTable("Running the detect command produces the correct error code", + DescribeTable( + "Running the detect command produces the correct error code", func(expectedErrorCode int, args ...string) { command := exec.Command(detectCmdPath, args...) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -18,10 +19,30 @@ var _ = Describe("Main", func() { Eventually(session).Should(gexec.Exit(expectedErrorCode)) }, - Entry("When the metadata contains a valid type", 0, "CHAINCODE_SOURCE_DIR", "./testdata/validtype"), - Entry("When the metadata contains an invalid type", 1, "CHAINCODE_SOURCE_DIR", "./testdata/invalidtype"), - Entry("When the metadata contents are invalid", 1, "CHAINCODE_SOURCE_DIR", "./testdata/invalidfile"), - Entry("When the metadata does not exist", 1, "CHAINCODE_SOURCE_DIR", "CHAINCODE_METADATA_DIR"), + Entry( + "When the metadata contains a valid type", + 0, + "CHAINCODE_SOURCE_DIR", + "./testdata/validtype", + ), + Entry( + "When the metadata contains an invalid type", + 1, + "CHAINCODE_SOURCE_DIR", + "./testdata/invalidtype", + ), + Entry( + "When the metadata contents are invalid", + 1, + "CHAINCODE_SOURCE_DIR", + "./testdata/invalidfile", + ), + Entry( + "When the metadata does not exist", + 1, + "CHAINCODE_SOURCE_DIR", + "CHAINCODE_METADATA_DIR", + ), Entry("When too few arguments are provided", 1, "CHAINCODE_SOURCE_DIR"), Entry( "When too many arguments are provided", diff --git a/cmd/release/main_test.go b/cmd/release/main_test.go index bb18950..156a364 100644 --- a/cmd/release/main_test.go +++ b/cmd/release/main_test.go @@ -50,7 +50,14 @@ var _ = Describe("Main", func() { Expect(indexPath).To(BeARegularFile()) textPath := filepath.Join(tempDir, "statedb", "couchdb", "indexes", "test.txt") Expect(textPath).NotTo(BeAnExistingFile()) - subdirPath := filepath.Join(tempDir, "statedb", "couchdb", "indexes", "subdir", "indexOwner.json") + subdirPath := filepath.Join( + tempDir, + "statedb", + "couchdb", + "indexes", + "subdir", + "indexOwner.json", + ) Expect(subdirPath).NotTo(BeAnExistingFile()) }) }) diff --git a/cmd/release/release_suite_test.go b/cmd/release/release_suite_test.go index 51e1149..d7c6156 100644 --- a/cmd/release/release_suite_test.go +++ b/cmd/release/release_suite_test.go @@ -2,6 +2,7 @@ package main_test import ( "testing" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -17,6 +18,8 @@ func TestRelease(t *testing.T) { } var _ = BeforeSuite(func() { + SetDefaultEventuallyTimeout(2 * time.Second) + var err error releaseCmdPath, err = gexec.Build("github.com/hyperledger-labs/fabric-builder-k8s/cmd/release") Expect(err).NotTo(HaveOccurred()) diff --git a/cmd/run/main.go b/cmd/run/main.go index b0a133f..9fb9702 100644 --- a/cmd/run/main.go +++ b/cmd/run/main.go @@ -54,12 +54,16 @@ func main() { } } + kubeServiceAccount := util.GetOptionalEnv(util.ChaincodeServiceAccountVariable, "default") + logger.Debugf("%s=%s", util.ChaincodeServiceAccountVariable, kubeServiceAccount) + run := &builder.Run{ BuildOutputDirectory: buildOutputDirectory, RunMetadataDirectory: runMetadataDirectory, PeerID: peerID, KubeconfigPath: kubeconfigPath, KubeNamespace: kubeNamespace, + KubeServiceAccount: kubeServiceAccount, } if err := run.Run(ctx); err != nil { diff --git a/cmd/run/main_test.go b/cmd/run/main_test.go index 52fbf51..0e31656 100644 --- a/cmd/run/main_test.go +++ b/cmd/run/main_test.go @@ -1,10 +1,14 @@ package main_test import ( + "fmt" + "os" "os/exec" + "github.com/bitfield/script" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) @@ -26,4 +30,64 @@ var _ = Describe("Main", func() { "UNEXPECTED_ARGUMENT", ), ) + + It( + "should start a chaincode pod using the supplied configuration environment variables", + Label("kind"), + func() { + homedir, err := os.UserHomeDir() + Expect(err).NotTo(HaveOccurred()) + + args := []string{"./testdata/validimage", "./testdata/validchaincode"} + command := exec.Command(runCmdPath, args...) + command.Env = append(os.Environ(), + fmt.Sprintf("KUBECONFIG_PATH=%s/.kube/config", homedir), + "CORE_PEER_ID=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789", + "FABRIC_K8S_BUILDER_DEBUG=true", + "FABRIC_K8S_BUILDER_NAMESPACE=chaincode", + "FABRIC_K8S_BUILDER_SERVICE_ACCOUNT=chaincode", + ) + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + Eventually(session).ShouldNot(gexec.Exit()) + Eventually( + session.Err, + ).Should(gbytes.Say(`run \[\d+\] DEBUG: FABRIC_K8S_BUILDER_NAMESPACE=chaincode`)) + Eventually( + session.Err, + ).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-mspid-core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789chai`)) + + pipe := script.Exec( + "kubectl wait --for=condition=ready pod --timeout=120s --namespace=chaincode -l fabric-builder-k8s-peerid=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789", + ) + _, err = pipe.Stdout() + Expect(err).NotTo(HaveOccurred()) + Expect(pipe.ExitStatus()).To(Equal(0)) + + descArgs := []string{ + "describe", + "pod", + "--namespace=chaincode", + "-l", + "fabric-builder-k8s-peerid=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789", + } + descCommand := exec.Command("kubectl", descArgs...) + descSession, err := gexec.Start(descCommand, GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + Eventually(descSession).Should(gexec.Exit(0)) + Eventually(descSession.Out).Should(gbytes.Say(`Namespace:\s+chaincode`)) + 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`)) + Eventually(descSession.Out).Should(gbytes.Say(`CORE_PEER_ADDRESS:\s+PEER_ADDRESS`)) + Eventually(descSession.Out).Should(gbytes.Say(`CORE_PEER_LOCALMSPID:\s+MSPID`)) + }, + ) }) diff --git a/cmd/run/run_suite_test.go b/cmd/run/run_suite_test.go index 2da9449..26119b9 100644 --- a/cmd/run/run_suite_test.go +++ b/cmd/run/run_suite_test.go @@ -1,27 +1,76 @@ package main_test import ( + "os" "testing" + "time" + "github.com/bitfield/script" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) //nolint:gochecknoglobals // not sure how to avoid this -var runCmdPath string +var ( + includeKindTests bool + runCmdPath string +) func TestRun(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Run Suite") + + suiteConfig, _ := GinkgoConfiguration() + + kindEnv := os.Getenv("INCLUDE_KIND_TESTS") + if kindEnv == "true" { + includeKindTests = true + } else { + includeKindTests = false + } + + if !includeKindTests { + if suiteConfig.LabelFilter == "" { + suiteConfig.LabelFilter = "!kind" + } else { + suiteConfig.LabelFilter = "(" + suiteConfig.LabelFilter + ") && !kind" + } + } + + RunSpecs(t, "Run Suite", suiteConfig) } var _ = BeforeSuite(func() { + SetDefaultEventuallyTimeout(2 * time.Second) + var err error runCmdPath, err = gexec.Build("github.com/hyperledger-labs/fabric-builder-k8s/cmd/run") Expect(err).NotTo(HaveOccurred()) + + if includeKindTests { + script.Exec("kind delete cluster --name fabric-builder-k8s-test") + + pipe := script.Exec("kind create cluster --name fabric-builder-k8s-test") + _, err = pipe.Stdout() + Expect(err).NotTo(HaveOccurred()) + Expect(pipe.ExitStatus()).To(Equal(0)) + + pipe = script.Exec("kubectl create namespace chaincode") + _, err = pipe.Stdout() + Expect(err).NotTo(HaveOccurred()) + Expect(pipe.ExitStatus()).To(Equal(0)) + + pipe = script.Exec("kubectl create serviceaccount chaincode --namespace=chaincode") + _, err = pipe.Stdout() + Expect(err).NotTo(HaveOccurred()) + Expect(pipe.ExitStatus()).To(Equal(0)) + } }) var _ = AfterSuite(func() { gexec.CleanupBuildArtifacts() + if includeKindTests { + _, err := script.Exec("kind delete cluster --name fabric-builder-k8s-test").Stdout() + Expect(err).NotTo(HaveOccurred()) + } }) diff --git a/cmd/run/testdata/validimage/image.json b/cmd/run/testdata/validimage/image.json index 313f1e6..ad643dc 100644 --- a/cmd/run/testdata/validimage/image.json +++ b/cmd/run/testdata/validimage/image.json @@ -1,4 +1,4 @@ { - "name": "ghcr.io/hyperledger/asset-transfer-basic", - "digest": "sha256:b35962f000d26ad046d4102f22d70a1351692fc69a9ddead89dfa13aefb942a7" + "name": "nginx", + "digest": "sha256:da3cc3053314be9ca3871307366f6e30ce2b11e1ea6a72e5957244d99b2515bf" } diff --git a/docs/HLF_OPERATOR.md b/docs/HLF_OPERATOR.md index 3f16879..de49231 100644 --- a/docs/HLF_OPERATOR.md +++ b/docs/HLF_OPERATOR.md @@ -28,6 +28,7 @@ kubectl patch peer org1-peer0 --type=json --patch-file=/dev/stdin <<-EOF "CORE_PEER_ID", "FABRIC_K8S_BUILDER_DEBUG", "FABRIC_K8S_BUILDER_NAMESPACE", + "FABRIC_K8S_BUILDER_SERVICE_ACCOUNT", "KUBERNETES_SERVICE_HOST", "KUBERNETES_SERVICE_PORT" ] diff --git a/go.mod b/go.mod index 902b1ef..ce657ee 100644 --- a/go.mod +++ b/go.mod @@ -12,11 +12,18 @@ require ( ) require ( + bitbucket.org/creachadair/shell v0.0.7 // indirect + github.com/itchyny/gojq v0.12.7 // indirect + github.com/itchyny/timefmt-go v0.1.3 // indirect +) + +require ( + github.com/bitfield/script v0.21.4 github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.2.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.5 // indirect + github.com/google/go-cmp v0.5.7 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.5 // indirect @@ -26,7 +33,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect diff --git a/go.sum b/go.sum index 670530a..48f88ab 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk= +bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -49,6 +51,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/bitfield/script v0.21.4 h1:XPMD/ti7pa9KW1aPMq7Hfh+mVznQdlqxkbiZSM2lnbE= +github.com/bitfield/script v0.21.4/go.mod h1:l3AZPVAtKQrL03bwh7nlNTUtgrgSWurpJSbtqspYrOA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -138,6 +142,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -173,6 +180,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/itchyny/gojq v0.12.7 h1:hYPTpeWfrJ1OT+2j6cvBScbhl0TkdwGM4bc66onUSOQ= +github.com/itchyny/gojq v0.12.7/go.mod h1:ZdvNHVlzPgUf8pgjnuDTmGfHA/21KoutQUJ3An/xNuw= +github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU= +github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -188,6 +199,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -404,9 +417,12 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= diff --git a/internal/builder/detect.go b/internal/builder/detect.go index d9988c6..658f818 100644 --- a/internal/builder/detect.go +++ b/internal/builder/detect.go @@ -7,7 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "path/filepath" "strings" @@ -32,7 +32,7 @@ func (d *Detect) Run(ctx context.Context) error { mdpath := filepath.Join(d.ChaincodeMetadataDirectory, "metadata.json") - mdbytes, err := ioutil.ReadFile(mdpath) + mdbytes, err := os.ReadFile(mdpath) if err != nil { return fmt.Errorf("unable to read %s: %w", mdpath, err) } diff --git a/internal/builder/run.go b/internal/builder/run.go index f306a9f..d7eb314 100644 --- a/internal/builder/run.go +++ b/internal/builder/run.go @@ -16,6 +16,7 @@ type Run struct { PeerID string KubeconfigPath string KubeNamespace string + KubeServiceAccount string } func (r *Run) Run(ctx context.Context) error { @@ -34,24 +35,53 @@ func (r *Run) Run(ctx context.Context) error { clientset, err := util.GetKubeClientset(logger, r.KubeconfigPath) if err != nil { - return fmt.Errorf("unable to connect kubernetes client for chaincode ID %s: %w", chaincodeData.ChaincodeID, err) + return fmt.Errorf( + "unable to connect kubernetes client for chaincode ID %s: %w", + chaincodeData.ChaincodeID, + err, + ) } secretsClient := clientset.CoreV1().Secrets(r.KubeNamespace) - err = util.ApplyChaincodeSecrets(ctx, logger, secretsClient, r.KubeNamespace, r.PeerID, chaincodeData) + err = util.ApplyChaincodeSecrets( + ctx, + logger, + secretsClient, + r.KubeNamespace, + r.PeerID, + chaincodeData, + ) if err != nil { - return fmt.Errorf("unable to create kubernetes secret for chaincode ID %s: %w", chaincodeData.ChaincodeID, err) + return fmt.Errorf( + "unable to create kubernetes secret for chaincode ID %s: %w", + chaincodeData.ChaincodeID, + err, + ) } podsClient := clientset.CoreV1().Pods(r.KubeNamespace) - pod, err := util.CreateChaincodePod(ctx, logger, podsClient, r.KubeNamespace, r.PeerID, chaincodeData, imageData) + pod, err := util.CreateChaincodePod( + ctx, + logger, + podsClient, + r.KubeNamespace, + r.KubeServiceAccount, + r.PeerID, + chaincodeData, + imageData, + ) if err != nil { return err } - logger.Printf("Running chaincode ID %s in kubernetes pod %s/%s", chaincodeData.ChaincodeID, pod.Namespace, pod.Name) + logger.Printf( + "Running chaincode ID %s in kubernetes pod %s/%s", + chaincodeData.ChaincodeID, + pod.Namespace, + pod.Name, + ) return util.WaitForChaincodePod(ctx, logger, podsClient, pod, chaincodeData.ChaincodeID) } diff --git a/internal/util/copy.go b/internal/util/copy.go index a9b9869..058dfb7 100644 --- a/internal/util/copy.go +++ b/internal/util/copy.go @@ -21,7 +21,12 @@ func CopyImageJSON(logger *log.CmdLogger, src, dest string) error { err := copy.Copy(imageSrcPath, imageDestPath) if err != nil { - return fmt.Errorf("failed to copy chaincode image file from %s to %s: %w", imageSrcPath, imageDestPath, err) + return fmt.Errorf( + "failed to copy chaincode image file from %s to %s: %w", + imageSrcPath, + imageDestPath, + err, + ) } logger.Debugf("Verifying chaincode image file %s", imageDestPath) @@ -53,7 +58,11 @@ func CopyIndexFiles(logger *log.CmdLogger, src, dest string) error { } if !fileInfo.IsDir() { - return fmt.Errorf("CouchDB index definitions path %s is not a directory: %w", indexSrcDir, err) + return fmt.Errorf( + "CouchDB index definitions path %s is not a directory: %w", + indexSrcDir, + err, + ) } opt := copy.Options{ @@ -63,7 +72,12 @@ func CopyIndexFiles(logger *log.CmdLogger, src, dest string) error { } if err := copy.Copy(indexSrcDir, indexDestDir, opt); err != nil { - return fmt.Errorf("failed to copy CouchDB index definitions from %s to %s: %w", indexSrcDir, indexDestDir, err) + return fmt.Errorf( + "failed to copy CouchDB index definitions from %s to %s: %w", + indexSrcDir, + indexDestDir, + err, + ) } return nil @@ -91,7 +105,12 @@ func CopyMetadataDir(logger *log.CmdLogger, src, dest string) error { } if err := copy.Copy(metadataSrcDir, metadataDestDir); err != nil { - return fmt.Errorf("failed to copy chaincode metadata from %s to %s: %w", metadataSrcDir, metadataDestDir, err) + return fmt.Errorf( + "failed to copy chaincode metadata from %s to %s: %w", + metadataSrcDir, + metadataDestDir, + err, + ) } return nil diff --git a/internal/util/env.go b/internal/util/env.go index 9972fe4..8e27a33 100644 --- a/internal/util/env.go +++ b/internal/util/env.go @@ -8,11 +8,12 @@ import ( ) const ( - builderVariablePrefix = "FABRIC_K8S_BUILDER_" - ChaincodeNamespaceVariable = builderVariablePrefix + "NAMESPACE" - DebugVariable = builderVariablePrefix + "DEBUG" - KubeconfigPathVariable = "KUBECONFIG_PATH" - PeerIDVariable = "CORE_PEER_ID" + builderVariablePrefix = "FABRIC_K8S_BUILDER_" + ChaincodeNamespaceVariable = builderVariablePrefix + "NAMESPACE" + ChaincodeServiceAccountVariable = builderVariablePrefix + "SERVICE_ACCOUNT" + DebugVariable = builderVariablePrefix + "DEBUG" + KubeconfigPathVariable = "KUBECONFIG_PATH" + PeerIDVariable = "CORE_PEER_ID" ) func GetOptionalEnv(key, defaultValue string) string { diff --git a/internal/util/k8s.go b/internal/util/k8s.go index 9e0bd2b..d8aa4f0 100644 --- a/internal/util/k8s.go +++ b/internal/util/k8s.go @@ -6,7 +6,7 @@ import ( "context" "encoding/base64" "fmt" - "io/ioutil" + "os" "regexp" "strings" "time" @@ -95,7 +95,11 @@ func waitForPodRunning( podRunningCondition := func(event watch.Event) (bool, error) { pod, ok := event.Object.(*apiv1.Pod) if !ok { - return false, fmt.Errorf("unexpected object while watching pod %s/%s", namespace, podName) + return false, fmt.Errorf( + "unexpected object while watching pod %s/%s", + namespace, + podName, + ) } phase := pod.Status.Phase @@ -122,7 +126,11 @@ func waitForPodTermination( pod, ok := event.Object.(*apiv1.Pod) if !ok { - return false, fmt.Errorf("unexpected object while watching pod %s/%s", namespace, podName) + return false, fmt.Errorf( + "unexpected object while watching pod %s/%s", + namespace, + podName, + ) } phase := pod.Status.Phase @@ -205,7 +213,7 @@ func GetKubeClientset(logger *log.CmdLogger, kubeconfigPath string) (*kubernetes } func GetKubeNamespace() (string, error) { - namespace, err := ioutil.ReadFile(namespacePath) + namespace, err := os.ReadFile(namespacePath) if err != nil { return "", fmt.Errorf("unable to read namespace from %s: %w", namespacePath, err) } @@ -213,10 +221,9 @@ func GetKubeNamespace() (string, error) { return string(namespace), nil } -//nolint:funlen // need to skip length check due to pod definition func getChaincodePodObject( imageData *ImageJSON, - namespace, podName, peerID string, + namespace, serviceAccount, podName, peerID string, chaincodeData *ChaincodeJSON, ) *apiv1.Pod { chaincodeImage := imageData.Name + "@" + imageData.Digest @@ -238,6 +245,7 @@ func getChaincodePodObject( }, }, Spec: apiv1.PodSpec{ + ServiceAccountName: serviceAccount, Containers: []apiv1.Container{ { Name: "main", @@ -295,7 +303,11 @@ func getChaincodePodObject( Name: "certs", VolumeSource: apiv1.VolumeSource{ Secret: &apiv1.SecretVolumeSource{ - SecretName: getSecretName(chaincodeData.MspID, peerID, chaincodeData.ChaincodeID), + SecretName: getSecretName( + chaincodeData.MspID, + peerID, + chaincodeData.ChaincodeID, + ), }, }, }, @@ -352,12 +364,21 @@ func ApplyChaincodeSecrets( ) error { secret := getChaincodeSecretApplyConfiguration(namespace, peerID, chaincodeData) - s, err := secretsClient.Apply(ctx, secret, metav1.ApplyOptions{FieldManager: "fabric-builder-k8s"}) + result, err := secretsClient.Apply( + ctx, + secret, + metav1.ApplyOptions{FieldManager: "fabric-builder-k8s"}, + ) if err != nil { return err } - logger.Debugf("Applied secrets for chaincode ID %s: %s/%s", chaincodeData.ChaincodeID, s.Namespace, s.Name) + logger.Debugf( + "Applied secrets for chaincode ID %s: %s/%s", + chaincodeData.ChaincodeID, + result.Namespace, + result.Name, + ) return nil } @@ -418,12 +439,19 @@ func CreateChaincodePod( ctx context.Context, logger *log.CmdLogger, podsClient v1.PodInterface, - namespace, peerID string, + namespace, serviceAccount, peerID string, chaincodeData *ChaincodeJSON, imageData *ImageJSON, ) (*apiv1.Pod, error) { podName := getPodName(chaincodeData.MspID, peerID, chaincodeData.ChaincodeID) - podDefinition := getChaincodePodObject(imageData, namespace, podName, peerID, chaincodeData) + podDefinition := getChaincodePodObject( + imageData, + namespace, + serviceAccount, + podName, + peerID, + chaincodeData, + ) err := deleteChaincodePod(ctx, logger, podsClient, podName, namespace, chaincodeData) if err != nil { diff --git a/samples/go-contract/main.go b/samples/go-contract/main.go index a95a8b0..317b3e7 100644 --- a/samples/go-contract/main.go +++ b/samples/go-contract/main.go @@ -11,12 +11,19 @@ type SampleContract struct { } // PutValue - Adds a key value pair to the world state -func (sc *SampleContract) PutValue(ctx contractapi.TransactionContextInterface, key string, value string) error { +func (sc *SampleContract) PutValue( + ctx contractapi.TransactionContextInterface, + key string, + value string, +) error { return ctx.GetStub().PutState(key, []byte(value)) } // GetValue - Gets the value for a key from the world state -func (sc *SampleContract) GetValue(ctx contractapi.TransactionContextInterface, key string) (string, error) { +func (sc *SampleContract) GetValue( + ctx contractapi.TransactionContextInterface, + key string, +) (string, error) { bytes, err := ctx.GetStub().GetState(key) if err != nil {