From 5327a4df5233902cb395442fc00d1d2d2d7a62ef Mon Sep 17 00:00:00 2001 From: Benjamin Lindner <50365642+lindnerby@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:33:06 +0200 Subject: [PATCH] chore: Modulectl release (#61) * chore: Bump go version and setup action * add dependabot actions scan * remove windows builds * rename workflow files to yml * add workflows * add version cmd * add release workflow * fix * fix * fix * gend ocs --- .github/scripts/create_changelog.sh | 36 +++++++++ .github/scripts/draft_release.sh | 34 ++++++++ .github/scripts/get_release_by_tag.sh | 25 ++++++ .github/scripts/publish_release.sh | 20 +++++ .github/scripts/upload_assets.sh | 58 +++++++++++++ .github/scripts/validate_release_tag.sh | 15 ++++ .github/workflows/{build.yaml => build.yml} | 1 - .github/workflows/create-release.yml | 81 +++++++++++++++++++ .../{test-unit.yaml => test-unit.yml} | 0 .../{validate-docs.yaml => validate-docs.yml} | 0 Makefile | 9 +-- cmd/main.go | 2 +- cmd/modulectl/cmd.go | 7 ++ cmd/modulectl/version/cmd.go | 29 +++++++ cmd/modulectl/version/cmd_test.go | 57 +++++++++++++ docs/gen-docs/modulectl.md | 1 + docs/gen-docs/modulectl_version.md | 25 ++++++ tests/e2e/create/create_test.go | 6 +- 18 files changed, 393 insertions(+), 13 deletions(-) create mode 100644 .github/scripts/create_changelog.sh create mode 100644 .github/scripts/draft_release.sh create mode 100644 .github/scripts/get_release_by_tag.sh create mode 100644 .github/scripts/publish_release.sh create mode 100644 .github/scripts/upload_assets.sh create mode 100644 .github/scripts/validate_release_tag.sh rename .github/workflows/{build.yaml => build.yml} (95%) create mode 100644 .github/workflows/create-release.yml rename .github/workflows/{test-unit.yaml => test-unit.yml} (100%) rename .github/workflows/{validate-docs.yaml => validate-docs.yml} (100%) create mode 100644 cmd/modulectl/version/cmd.go create mode 100644 cmd/modulectl/version/cmd_test.go create mode 100644 docs/gen-docs/modulectl_version.md diff --git a/.github/scripts/create_changelog.sh b/.github/scripts/create_changelog.sh new file mode 100644 index 00000000..19b9c067 --- /dev/null +++ b/.github/scripts/create_changelog.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + + +set -o errexit +set -E +set -o pipefail + +CURRENT_RELEASE_TAG=$1 +DOCKER_IMAGE_URL=$2 +LAST_RELEASE_TAG=$3 + +if [ "${LAST_RELEASE_TAG}" == "" ] +then + LAST_RELEASE_TAG=$(git describe --tags --abbrev=0) +fi + +GITHUB_URL=https://api.github.com/repos/$CODE_REPOSITORY +GITHUB_AUTH_HEADER="Authorization: Bearer $GITHUB_TOKEN" +CHANGELOG_FILE="CHANGELOG.md" + +git log "$LAST_RELEASE_TAG"..HEAD --pretty=tformat:"%h" --reverse | while read -r commit +do + COMMIT_AUTHOR=$(curl -H "$GITHUB_AUTH_HEADER" -sS "$GITHUB_URL"/commits/"$commit" | jq -r '.author.login') + if [ "${COMMIT_AUTHOR}" != "kyma-bot" ]; then + git show -s "${commit}" --format="* %s by @${COMMIT_AUTHOR}" >> ${CHANGELOG_FILE} + fi +done + +{ + echo -e "\n**Full changelog**: $GITHUB_URL/compare/$LAST_RELEASE_TAG...$CURRENT_RELEASE_TAG" + echo -e "\n" + echo "## Docker image URL" + echo "$DOCKER_IMAGE_URL" +} >> $CHANGELOG_FILE + +cat $CHANGELOG_FILE diff --git a/.github/scripts/draft_release.sh b/.github/scripts/draft_release.sh new file mode 100644 index 00000000..81b638b7 --- /dev/null +++ b/.github/scripts/draft_release.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -E +set -o pipefail + +RELEASE_TAG=$1 + +GITHUB_URL=https://api.github.com/repos/${CODE_REPOSITORY} +GITHUB_AUTH_HEADER="Authorization: Bearer ${GITHUB_TOKEN}" +CHANGELOG_FILE=$(cat CHANGELOG.md) + +JSON_PAYLOAD=$(jq -n \ + --arg tag_name "$RELEASE_TAG" \ + --arg name "$RELEASE_TAG" \ + --arg body "$CHANGELOG_FILE" \ + '{ + "tag_name": $tag_name, + "name": $name, + "body": $body, + "draft": true + }') + +CURL_RESPONSE=$(curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "${GITHUB_AUTH_HEADER}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "${GITHUB_URL}"/releases \ + -d "$JSON_PAYLOAD") + +# return the id of the release draft +echo "$CURL_RESPONSE" | jq -r ".id" diff --git a/.github/scripts/get_release_by_tag.sh b/.github/scripts/get_release_by_tag.sh new file mode 100644 index 00000000..00f680dc --- /dev/null +++ b/.github/scripts/get_release_by_tag.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -o nounset +set -o pipefail + +RELEASE_TAG=$1 +GITHUB_TOKEN=$2 + +GITHUB_URL=https://api.github.com/repos/$CODE_REPOSITORY +GITHUB_AUTH_HEADER="Authorization: Bearer $GITHUB_TOKEN" + +curl -L \ + -s \ + --fail-with-body \ + -H "Accept: application/vnd.github+json" \ + -H "$GITHUB_AUTH_HEADER" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "$GITHUB_URL"/releases/tags/"$RELEASE_TAG" + +CURL_EXIT_CODE=$? + +if [[ $CURL_EXIT_CODE == 0 ]]; then + echo "Release with tag $RELEASE_TAG already exists!" + exit 1 +fi diff --git a/.github/scripts/publish_release.sh b/.github/scripts/publish_release.sh new file mode 100644 index 00000000..d4c0d38b --- /dev/null +++ b/.github/scripts/publish_release.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -E +set -o pipefail + +RELEASE_ID=$1 + +GITHUB_URL=https://api.github.com/repos/${CODE_REPOSITORY} +GITHUB_AUTH_HEADER="Authorization: Bearer ${GITHUB_TOKEN}" + +CURL_RESPONSE=$(curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "${GITHUB_AUTH_HEADER}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "${GITHUB_URL}"/releases/"${RELEASE_ID}" \ + -d '{"draft":false}') +echo "$CURL_RESPONSE" diff --git a/.github/scripts/upload_assets.sh b/.github/scripts/upload_assets.sh new file mode 100644 index 00000000..a26f6a7c --- /dev/null +++ b/.github/scripts/upload_assets.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -E +set -o pipefail + +uploadFile() { + filePath=${1} + ghAsset=${2} + + echo "Uploading ${filePath} as ${ghAsset}" + response=$(curl -s -o output.txt -w "%{http_code}" \ + --request POST --data-binary @"$filePath" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: text/yaml" \ + "$ghAsset") + if [[ "$response" != "201" ]]; then + echo "Unable to upload the asset ($filePath): " + echo "HTTP Status: $response" + cat output.txt + exit 1 + else + echo "$filePath uploaded" + fi +} + +echo "PULL_BASE_REF= ${PULL_BASE_REF}" + +echo "Fetching releases" +CURL_RESPONSE=$(curl -w "%{http_code}" -sL \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN"\ + https://api.github.com/repos/kyma-project/modulectl/releases) +JSON_RESPONSE=$(sed '$ d' <<< "${CURL_RESPONSE}") +HTTP_CODE=$(tail -n1 <<< "${CURL_RESPONSE}") +if [[ "${HTTP_CODE}" != "200" ]]; then + echo "${CURL_RESPONSE}" + exit 1 +fi + +echo "Finding release id for: ${PULL_BASE_REF}" +RELEASE_ID=$(jq <<< "${JSON_RESPONSE}" --arg tag "${PULL_BASE_REF}" '.[] | select(.tag_name == $ARGS.named.tag) | .id') + +echo "Got '${RELEASE_ID}' release id" +if [ -z "${RELEASE_ID}" ] +then + echo "No release with tag = ${PULL_BASE_REF}" + exit 1 +fi + +echo "Adding assets to Github release" +UPLOAD_URL="https://uploads.github.com/repos/kyma-project/modulectl/releases/${RELEASE_ID}/assets" + +echo "$UPLOAD_URL" +pwd +ls -la +uploadFile "modulectl-linux" "${UPLOAD_URL}?name=modulectl-linux" diff --git a/.github/scripts/validate_release_tag.sh b/.github/scripts/validate_release_tag.sh new file mode 100644 index 00000000..5c0c7a73 --- /dev/null +++ b/.github/scripts/validate_release_tag.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -E +set -o pipefail + +CURRENT_RELEASE_TAG=$1 + +semver_pattern="^([0-9]|[1-9][0-9]*)[.]([0-9]|[1-9][0-9]*)[.]([0-9]|[1-9][0-9]*)(-[a-z][a-z0-9]*)?$" + +if ! [[ $CURRENT_RELEASE_TAG =~ $semver_pattern ]]; then + echo "Given tag \"$CURRENT_RELEASE_TAG\" does not match semantic version pattern: \"$semver_pattern\"." + exit 1 +fi diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yml similarity index 95% rename from .github/workflows/build.yaml rename to .github/workflows/build.yml index f07ff0f2..8012ff3f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yml @@ -3,7 +3,6 @@ on: pull_request: branches: - main - - 'release-**' workflow_dispatch: jobs: build-modulectl: diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 00000000..1a9633fa --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,81 @@ +name: "Create release" + +on: + workflow_dispatch: + inputs: + version: + description: "Release version" + default: "" + required: true + since: + description: "Changelog since" + default: "" + required: false + +jobs: + validate-release: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Validate the release tag + run: ./.github/scripts/validate_release_tag.sh ${{ github.event.inputs.version }} + - name: Check if release exists + run: ./.github/scripts/get_release_by_tag.sh ${{ github.event.inputs.version }} ${{ secrets.GITHUB_TOKEN }} + draft-release: + runs-on: ubuntu-latest + needs: validate-release + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Create changelog + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./.github/scripts/create_changelog.sh ${{ github.event.inputs.version }} ${{ env.IMAGE_REPO }}:${{ github.event.inputs.version }} ${{ github.event.inputs.since }} + - name: Draft release + id: draft-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + RELEASE_ID=$(./.github/scripts/draft_release.sh ${{ github.event.inputs.version }}) + echo "release_id=$RELEASE_ID" >> $GITHUB_OUTPUT + - name: Create tag + run: | + git tag ${{ github.event.inputs.version }} + git push origin ${{ github.event.inputs.version }} --tags + outputs: + release_id: ${{ steps.draft-release.outputs.release_id }} + artifacts: + runs-on: ubuntu-latest + needs: validate-release + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Go setup + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache-dependency-path: 'go.sum' + - name: "Run 'make build' with version" + run: make build VERSION=${{ inputs.version }} + - name: Add binaries to draft + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_BASE_REF: ${{ github.event.inputs.version }} + run: ./.github/scripts/upload_assets.sh + publish-release: + runs-on: ubuntu-latest + needs: [ validate-release, draft-release, artifacts ] + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Publish release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./.github/scripts/publish_release.sh ${{ needs.draft-release.outputs.release_id }} diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yml similarity index 100% rename from .github/workflows/test-unit.yaml rename to .github/workflows/test-unit.yml diff --git a/.github/workflows/validate-docs.yaml b/.github/workflows/validate-docs.yml similarity index 100% rename from .github/workflows/validate-docs.yaml rename to .github/workflows/validate-docs.yml diff --git a/Makefile b/Makefile index 17d9d9ad..2daf466e 100644 --- a/Makefile +++ b/Makefile @@ -23,10 +23,7 @@ FLAGS = -ldflags '-s -w -X github.com/kyma-project/modulectl/cmd/modulectl/versi validate-docs: ./hack/verify-generated-docs.sh -build: build-windows build-linux build-darwin build-windows-arm build-linux-arm build-darwin-arm - -build-windows: - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./bin/modulectl.exe $(FLAGS) ./cmd +build: build-linux build-darwin build-linux-arm build-darwin-arm build-darwin: CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./bin/modulectl-darwin $(FLAGS) ./cmd @@ -34,10 +31,6 @@ build-darwin: build-linux: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/modulectl-linux $(FLAGS) ./cmd -# ARM based chipsets -build-windows-arm: - CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -o ./bin/modulectl-arm.exe $(FLAGS) ./cmd - build-darwin-arm: CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o ./bin/modulectl-darwin-arm $(FLAGS) ./cmd diff --git a/cmd/main.go b/cmd/main.go index d71b10b7..566f3c07 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,7 +14,7 @@ func main() { os.Exit(-1) } - if err := cmd.Execute(); err != nil { + if err = cmd.Execute(); err != nil { fmt.Println(fmt.Errorf("failed to execute modulectl command: %w", err)) os.Exit(-1) } diff --git a/cmd/modulectl/cmd.go b/cmd/modulectl/cmd.go index 02905127..75cce5cd 100644 --- a/cmd/modulectl/cmd.go +++ b/cmd/modulectl/cmd.go @@ -9,6 +9,7 @@ import ( createcmd "github.com/kyma-project/modulectl/cmd/modulectl/create" scaffoldcmd "github.com/kyma-project/modulectl/cmd/modulectl/scaffold" + "github.com/kyma-project/modulectl/cmd/modulectl/version" "github.com/kyma-project/modulectl/internal/service/componentarchive" "github.com/kyma-project/modulectl/internal/service/componentdescriptor" "github.com/kyma-project/modulectl/internal/service/contentprovider" @@ -72,8 +73,14 @@ func NewCmd() (*cobra.Command, error) { return nil, fmt.Errorf("failed to build create command: %w", err) } + versionCmd, err := version.NewCmd() + if err != nil { + return nil, fmt.Errorf("failed to build version command: %w", err) + } + rootCmd.AddCommand(scaffoldCmd) rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(versionCmd) return rootCmd, nil } diff --git a/cmd/modulectl/version/cmd.go b/cmd/modulectl/version/cmd.go new file mode 100644 index 00000000..71927576 --- /dev/null +++ b/cmd/modulectl/version/cmd.go @@ -0,0 +1,29 @@ +package version + +import ( + "github.com/spf13/cobra" +) + +const ( + use = "version" + short = "Prints the current modulectl version." + long = "This command prints the current semantic version of the modulectl binary set at build time." +) + +// Version will contain the binary version injected by make build target +var Version string //nolint:gochecknoglobals // This is a variable meant to be set at build time + +func NewCmd() (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: use, + Short: short, + Long: long, + Args: cobra.NoArgs, + Aliases: []string{"v"}, + Run: func(cmd *cobra.Command, args []string) { + cmd.Println(Version) + }, + } + + return cmd, nil +} diff --git a/cmd/modulectl/version/cmd_test.go b/cmd/modulectl/version/cmd_test.go new file mode 100644 index 00000000..64d2ea5a --- /dev/null +++ b/cmd/modulectl/version/cmd_test.go @@ -0,0 +1,57 @@ +package version_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/kyma-project/modulectl/cmd/modulectl/version" +) + +func TestNewCmd_WhenCalled_ReturnsNoErr(t *testing.T) { + _, err := version.NewCmd() + + require.NoError(t, err) +} + +func TestNewCmd_WhenCalled_CmdContainsUseDescription(t *testing.T) { + cmd, _ := version.NewCmd() + + assert.Equal(t, "version", cmd.Use) +} + +func TestNewCmd_WhenCalled_CmdContainsShortDescription(t *testing.T) { + cmd, _ := version.NewCmd() + + assert.Equal(t, "Prints the current modulectl version.", cmd.Short) +} + +func TestNewCmd_WhenCalled_CmdContainsLongDescription(t *testing.T) { + cmd, _ := version.NewCmd() + + assert.Equal(t, "This command prints the current semantic version of the modulectl binary set at build time.", cmd.Long) +} + +func TestNewCmd_WhenCalled_CmdRunNotNil(t *testing.T) { + cmd, _ := version.NewCmd() + + require.NotNil(t, cmd.Run) +} + +func TestNewCmd_WhenCalled_CmdHasAlias(t *testing.T) { + cmd, _ := version.NewCmd() + + require.Len(t, cmd.Aliases, 1) + require.Contains(t, cmd.Aliases, "v") +} + +func TestNewCmd_WhenCalled_CmdExecuteCanBeCalledWithNoVersionGlobalSet(t *testing.T) { + cmd, _ := version.NewCmd() + os.Args = []string{"version"} + + err := cmd.Execute() + + require.NoError(t, err) +} diff --git a/docs/gen-docs/modulectl.md b/docs/gen-docs/modulectl.md index e81348ba..6edc1e32 100644 --- a/docs/gen-docs/modulectl.md +++ b/docs/gen-docs/modulectl.md @@ -21,4 +21,5 @@ A CLI from the Kyma Module Controller. Wonderful to use. * [modulectl create](modulectl_create.md) - Creates a module bundled as an OCI artifact. * [modulectl scaffold](modulectl_scaffold.md) - Generates necessary files required for module creation. +* [modulectl version](modulectl_version.md) - Prints the current modulectl version. diff --git a/docs/gen-docs/modulectl_version.md b/docs/gen-docs/modulectl_version.md new file mode 100644 index 00000000..50cd02a0 --- /dev/null +++ b/docs/gen-docs/modulectl_version.md @@ -0,0 +1,25 @@ +--- +title: modulectl version +--- + +Prints the current modulectl version. + +## Synopsis + +This command prints the current semantic version of the modulectl binary set at build time. + +```bash +modulectl version [flags] +``` + +## Flags + +```bash +-h, --help Provides help for the version command. +``` + +## See also + +* [modulectl](modulectl.md) - This is the Kyma Module Controller CLI. + + diff --git a/tests/e2e/create/create_test.go b/tests/e2e/create/create_test.go index 459f2617..d1d3c580 100644 --- a/tests/e2e/create/create_test.go +++ b/tests/e2e/create/create_test.go @@ -309,7 +309,7 @@ var _ = Describe("Test 'create' command", Ordered, func() { Expect(resource.Name).To(Equal("template-operator")) Expect(resource.Relation).To(Equal(ocmv1.ExternalRelation)) Expect(resource.Type).To(Equal("ociArtifact")) - Expect(resource.Version).To(Equal("1.0.0")) + Expect(resource.Version).To(Equal("1.0.1")) resource = descriptor.Resources[1] Expect(resource.Name).To(Equal("raw-manifest")) Expect(resource.Version).To(Equal("1.0.3")) @@ -320,7 +320,7 @@ var _ = Describe("Test 'create' command", Ordered, func() { ociArtifactAccessSpec, ok := resourceAccessSpec0.(*ociartifact.AccessSpec) Expect(ok).To(BeTrue()) Expect(ociArtifactAccessSpec.GetType()).To(Equal(ociartifact.Type)) - Expect(ociArtifactAccessSpec.ImageReference).To(Equal("europe-docker.pkg.dev/kyma-project/prod/template-operator:1.0.0")) + Expect(ociArtifactAccessSpec.ImageReference).To(Equal("europe-docker.pkg.dev/kyma-project/prod/template-operator:1.0.1")) By("And descriptor.component.resources[1].access should be correct") resourceAccessSpec1, err := ocm.DefaultContext().AccessSpecForSpec(descriptor.Resources[1].Access) @@ -347,7 +347,7 @@ var _ = Describe("Test 'create' command", Ordered, func() { By("And security scan labels should be correct") secScanLabels := flatten(descriptor.Sources[0].Labels) Expect(secScanLabels).To(HaveKeyWithValue("git.kyma-project.io/ref", "HEAD")) - Expect(secScanLabels).To(HaveKeyWithValue("scan.security.kyma-project.io/rc-tag", "1.0.0")) + Expect(secScanLabels).To(HaveKeyWithValue("scan.security.kyma-project.io/rc-tag", "1.0.1")) Expect(secScanLabels).To(HaveKeyWithValue("scan.security.kyma-project.io/language", "golang-mod")) Expect(secScanLabels).To(HaveKeyWithValue("scan.security.kyma-project.io/dev-branch", "main")) Expect(secScanLabels).To(HaveKeyWithValue("scan.security.kyma-project.io/subprojects", "false"))