diff --git a/.github/.goreleaser.yml b/.github/.goreleaser.yml new file mode 100644 index 0000000000..bed8c71424 --- /dev/null +++ b/.github/.goreleaser.yml @@ -0,0 +1,63 @@ +project_name: kanvas-snapshot +version: 2 +before: + hooks: + - go mod tidy + +builds: + - main: cmd/kanvas-snapshot/main.go + + env: + - CGO_ENABLED=0 + + ldflags: + - -s -w + - -X main.providerToken={{.Env.PROVIDER_TOKEN}} + - -X main.MesheryCloudApiBaseUrl="https://meshery.layer5.io" + - -X main.MesheryApiBaseUrl="https://playground.meshery.io" + + goos: + - darwin + - linux + - windows + + goarch: + - 386 + - amd64 + - arm + - arm64 + + ignore: + - goos: windows + goarch: arm + - goos: windows + goarch: arm64 + +archives: + - id: stable + name_template: >- + {{ .ProjectName }}_{{.Version}}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + format: tar.gz + format_overrides: + - goos: windows + format: zip + +checksum: + name_template: 'checksums.txt' + +snapshot: + version_template: "{{ .Tag }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +release: + name_template: "Helm Kanvas Snapshot {{.Tag}}" diff --git a/.github/workflows/build-and-release-snapshot-plugin.yml b/.github/workflows/build-and-release-snapshot-plugin.yml index cfc2df49f0..015ab25224 100644 --- a/.github/workflows/build-and-release-snapshot-plugin.yml +++ b/.github/workflows/build-and-release-snapshot-plugin.yml @@ -34,15 +34,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_CHANNEL: "stable" - MESHERY_CLOUD_API_COOKIE: ${{ secrets.MESHERY_CLOUD_API_COOKIE }} - MESHERY_API_COOKIE: ${{ secrets.MESHERY_API_COOKIE }} - OWNER: ${{ secrets.OWNER }} - REPO: ${{ secrets.REPO }} - WORKFLOW: ${{ secrets.WORKFLOW }} - BRANCH: ${{ secrets.BRANCH }} - MESHERY_CLOUD_API_BASE_URL: ${{ secrets.MESHERY_CLOUD_API_BASE_URL }} - MESHERY_API_BASE_URL: ${{ secrets.MESHERY_API_BASE_URL }} - SYSTEM_ID: ${{ secrets.SYSTEM_ID }} + PROVIDER_TOKEN: ${{ secrets.PROVIDER_TOKEN }} + with: - version: '~> v2' - args: release --clean --skip-validate + version: 2 + args: release --clean --skip validate -f .github/.goreleaser.yml diff --git a/.github/workflows/go-testing-ci.yml b/.github/workflows/go-testing-ci.yml new file mode 100644 index 0000000000..4919f6cb16 --- /dev/null +++ b/.github/workflows/go-testing-ci.yml @@ -0,0 +1,92 @@ +name: Golang Unit and Integration Tests +on: + push: + branches: + - "master" + paths: + - "**.go" + pull_request: + branches: + - "master" + paths: + - "**.go" + workflow_dispatch: + inputs: + logLevel: + description: "Log level" + required: true + default: "warning" + +jobs: + golangci: + strategy: + matrix: + go: [1.21] + os: [ubuntu-22.04] + name: golangci-lint + if: github.repository == 'meshery/helm-kanvas-snapshot' + runs-on: ${{ matrix.os }} + steps: + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + cache: false + - uses: actions/checkout@v4 + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.59 + args: --config=.golangci.yml --timeout=10m + unit-tests: + name: Unit tests + runs-on: ubuntu-22.04 + needs: golangci + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" + - name: Run coverage + run: go test --short ./... -race -coverprofile=coverage.txt -covermode=atomic + - name: Upload coverage to Codecov + if: github.repository == 'meshery/helm-kanvas-snapshot' + uses: codecov/codecov-action@v4 + with: + files: ./coverage.txt + flags: unittests + integration-tests: + name: Integration tests + runs-on: ubuntu-22.04 + needs: golangci + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" + - name: Install Docker Compose + run: | + sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Create k8s Kind Cluster + uses: helm/kind-action@v1.10.0 + with: + cluster_name: "kind-cluster" + - name: Run coverage + run: + # TODO: add tests for snapshot + echo "Running kanvas snapshot test completed." + + - name: Upload coverage to Codecov + if: github.repository == 'meshery/helm-kanvas-snapshot' + uses: codecov/codecov-action@v4 + with: + files: ./coverage.txt + flags: gointegrationtests diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 9ab267d962..b31293ae70 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -58,10 +58,10 @@ jobs: restore-keys: | ${{ runner.os }}-go- - name: goreleaser WITHOUT tag - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v6 if: success() && startsWith(github.ref, 'refs/tags/') == false env: RELEASE_CHANNEL: "edge" with: - version: latest - args: release --snapshot --skip-publish --clean + version: 2 + args: release --snapshot --skip publish --clean -f .github/.goreleaser.yml diff --git a/.github/workflows/meshmap.yml b/.github/workflows/meshmap.yml deleted file mode 100644 index 0b19d21c67..0000000000 --- a/.github/workflows/meshmap.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Kanvas Snapshot Service -on: # rebuild any PRs and main branch changes - pull_request_target: - types: [opened, synchronize, reopened] - - workflow_call: - inputs: - fileName: - description: Relative file path from the root directory - required: true - type: string - outputs: - resource_url: - description: "The URL of the generated resource." - value: ${{ jobs.MeshMapScreenshot.outputs.resource_url }} -permissions: - actions: read - contents: write - security-events: write - statuses: write - pull-requests: write - id-token: write - -jobs: - MeshMapScreenshot: - runs-on: ubuntu-latest - outputs: - resource_url: ${{ steps.test_result.outputs.resource_url }} - steps: - - name: Set PR number # To comment the final status on the Pull-request opened in any repository - run: | - export pull_number=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") - echo "PULL_NO=$pull_number" >> $GITHUB_ENV - - uses: actions/checkout@v4 - - uses: actions/checkout@v4 #this step would go away - with: - path: action - repository: layer5labs/kanvas-snapshot - - id: test_result - uses: layer5labs/kanvas-snapshot@v0.2.13 - with: - githubToken: ${{ secrets.GITHUB_TOKEN }} # github's personal access token example: "ghp_...." - mesheryToken: ${{ secrets.MESHERY_TOKEN }} # Meshery Cloud Authentication token, signin to meshery-cloud to get one, example: ey..... - prNumber: ${{ env.PULL_NO }} # auto-filled from the above step - application_type: Kubernetes Manifest # your application type, could be any of three: "Kubernetes Manifest", "Docker Compose", "Helm Chart" - filePath: ${{ inputs.fileName == '' && 'install/deployment_yamls/k8s' || inputs.fileName }} # relative file-path from the root directory in the github-runner env, you might require to checkout the repository as described in step 2 diff --git a/Makefile b/Makefile index e1aa64058e..9288ead354 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,8 @@ include build/Makefile.core.mk .PHONY: all all: dep-check build ## Lint check -# golangci: error dep-check -# golangci-lint run --exclude-use-default +golangci: error dep-check + golangci-lint run --exclude-use-default ## Analyze error codes error: dep-check @@ -58,16 +58,9 @@ BINNAME_WINDOWS ?= kanvas-snapshot-windows-$(ARCH).exe LDFLAGS := "\ - -X 'main.GithubToken=$(GITHUB_TOKEN)' \ - -X 'main.MesheryCloudApiCookie=$(MESHERY_CLOUD_API_COOKIES)' \ - -X 'main.MesheryApiCookie=$(MESHERY_API_COOKIES)' \ - -X 'main.Owner=$(OWNER)' \ - -X 'main.Repo=$(REPO)' \ - -X 'main.Workflow=$(WORKFLOW)' \ - -X 'main.Branch=$(BRANCH)' \ + -X 'main.providerToken=$(PROVIDER_TOKEN)' \ -X 'main.MesheryCloudApiBaseUrl=$(MESHERY_CLOUD_API_BASE_URL)' \ - -X 'main.MesheryApiBaseUrl=$(MESHERY_API_BASE_URL)' \ - -X 'main.SystemID=$(SYSTEM_ID)'" + -X 'main.MesheryApiBaseUrl=$(MESHERY_API_BASE_URL)'" .PHONY: build build: diff --git a/build/Makefile.core.mk b/build/Makefile.core.mk index 7c87132bf8..543b87375f 100644 --- a/build/Makefile.core.mk +++ b/build/Makefile.core.mk @@ -1,11 +1,4 @@ GOVERSION = 1.23 -GITHUB_TOKEN="" -MESHERY_API_COOKIE="" -MESHERY_CLOUD_API_COOKIE="" -OWNER="Aijeyomah" -REPO="Ng-depl" -WORKFLOW="meshmap.yml" -BRANCH="new-mesh" +PROVIDER_TOKEN="dev_token" MESHERY_CLOUD_API_BASE_URL="http://localhost:9876" -MESHERY_API_BASE_URL="http://localhost:9081" -SYSTEM_ID="3ae41e77-5626-42d3-aa04-ee871ad3035c" +MESHERY_API_BASE_URL="http://localhost:3000" diff --git a/cmd/kanvas-snapshot/main.go b/cmd/kanvas-snapshot/main.go index cafdeeaa5e..fcfea0b04b 100644 --- a/cmd/kanvas-snapshot/main.go +++ b/cmd/kanvas-snapshot/main.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "path/filepath" "strings" @@ -18,19 +19,10 @@ import ( ) var ( - GithubToken string - MesheryToken string - MesheryCloudApiCookie string - MesheryApiCookie string - Owner string - Repo string - Workflow string - Branch string + ProviderToken string MesheryApiBaseUrl string MesheryCloudApiBaseUrl string - SystemID string Log logger.Handler - LogError logger.Handler ) var ( @@ -133,10 +125,15 @@ func ExtractNameFromURI(uri string) string { } func handleError(err error) { - if err != nil { - LogError.Error(err) - os.Exit(1) + if err == nil { + return + } + if Log != nil { + Log.Error(err) + } else { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) } + os.Exit(1) } func CreateMesheryDesign(uri, name, email string) (string, error) { @@ -149,58 +146,64 @@ func CreateMesheryDesign(uri, name, email string) (string, error) { payloadBytes, err := json.Marshal(payload) if err != nil { - LogError.Error(err) - os.Exit(1) + Log.Info("Failed to marshal payload:", err) + return "", errors.ErrDecodingAPI(err) } + sourceType := "Helm Chart" - req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/pattern/%s", MesheryApiBaseUrl, sourceType), bytes.NewBuffer(payloadBytes)) + encodedChartType := url.PathEscape(sourceType) + fullURL := fmt.Sprintf("%s/api/pattern/%s", MesheryApiBaseUrl, encodedChartType) + + // Create the request + req, err := http.NewRequest("POST", fullURL, bytes.NewBuffer(payloadBytes)) if err != nil { - LogError.Error(err) - os.Exit(1) + Log.Info("Failed to create new request:", err) + return "", errors.ErrHTTPPostRequest(err) } - req.Header.Set("Cookie", MesheryApiCookie) + // Set headers and log them + req.Header.Set("Cookie", ProviderToken) req.Header.Set("Origin", MesheryApiBaseUrl) req.Header.Set("Host", MesheryApiBaseUrl) - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", "text/plain;charset=UTF-8") req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd") req.Header.Set("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") client := &http.Client{} + resp, err := client.Do(req) if err != nil { - return "", err + return "", errors.ErrHTTPPostRequest(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - io.ReadAll(resp.Body) - return "", err + body, _ := io.ReadAll(resp.Body) + return "", errors.ErrUnexpectedResponseCode(resp.StatusCode, string(body)) } - // Expecting a JSON array in the response + + // Decode response var result []map[string]interface{} err = json.NewDecoder(resp.Body).Decode(&result) if err != nil { - return "", err + body, _ := io.ReadAll(resp.Body) + return "", errors.ErrDecodingAPI(fmt.Errorf("failed to decode json. body: %s, error: %w", body, err)) } + // Extracting the design ID from the result if len(result) > 0 { if id, ok := result[0]["id"].(string); ok { + Log.Infof("Successfully created Meshery design. ID: %s", id) return id, nil } } - return "", errors.ErrHTTPPostRequest(err) + return "", errors.ErrCreatingMesheryDesign(fmt.Errorf("failed to extract design ID from response")) } func GenerateSnapshot(designID, chartURI, email, assetLocation string) error { payload := map[string]interface{}{ - "Owner": Owner, - "Repo": Repo, - "Workflow": Workflow, - "Branch": Branch, - "github_token": GithubToken, "Payload": map[string]string{ "application_type": "Helm Chart", "designID": designID, @@ -225,20 +228,29 @@ func GenerateSnapshot(designID, chartURI, email, assetLocation string) error { return err } - req.Header.Set("Cookie", MesheryCloudApiCookie) + req.Header.Set("Cookie", ProviderToken) req.Header.Set("Content-Type", "application/json") - req.Header.Set("SystemID", SystemID) req.Header.Set("Referer", fmt.Sprintf("%s/dashboard", MesheryCloudApiBaseUrl)) client := &http.Client{} + resp, err := client.Do(req) if err != nil { - return err + return errors.ErrHTTPPostRequest(err) } defer resp.Body.Close() - if resp.StatusCode != 200 { - _, err := io.ReadAll(resp.Body) - return err + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return errors.ErrUnexpectedResponseCode(resp.StatusCode, string(body)) + } + + // Decode response + var result []map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + body, _ := io.ReadAll(resp.Body) + return errors.ErrDecodingAPI(fmt.Errorf("failed to decode json. body: %s, error: %w", body, err)) } return nil @@ -247,14 +259,14 @@ func GenerateSnapshot(designID, chartURI, email, assetLocation string) error { func main() { generateKanvasSnapshotCmd.Flags().StringVarP(&chartURI, "file", "f", "", "URI to Helm chart (required)") - generateKanvasSnapshotCmd.Flags().StringVarP(&designName, "design-name", "l", "", "Optional name for the Meshery design") + generateKanvasSnapshotCmd.Flags().StringVarP(&designName, "design-name", "n", "", "Optional name for the Meshery design") generateKanvasSnapshotCmd.Flags().StringVarP(&email, "email", "e", "", "Optional email to associate with the Meshery design") - generateKanvasSnapshotCmd.MarkFlagRequired("file") - generateKanvasSnapshotCmd.MarkFlagRequired("email") + _ = generateKanvasSnapshotCmd.MarkFlagRequired("file") + _ = generateKanvasSnapshotCmd.MarkFlagRequired("email") if err := generateKanvasSnapshotCmd.Execute(); err != nil { - LogError.Error(err) + Log.Error(err) os.Exit(1) } diff --git a/internal/errors/error.go b/internal/errors/error.go index e21a1c26fb..7338417035 100644 --- a/internal/errors/error.go +++ b/internal/errors/error.go @@ -1,16 +1,18 @@ package errors import ( + "fmt" + "github.com/layer5io/meshkit/errors" ) var ( - ErrInvalidChartURICode = "kanvas-snapshot-1000" - ErrCreatingMesheryDesignCode = "kanvas-snapshot-1001" - ErrGeneratingSnapshotCode = "kanvas-snapshot-1002" - ErrHTTPPostRequestCode = "kanvas-snapshot-1003" - ErrDecodingAPICode = "kanvas-snapshot-1004" - ErrRequiredFieldNotProvidedCode = "kanvas-snapshot-1005" + ErrInvalidChartURICode = "kanvas-snapshot-900" + ErrCreatingMesheryDesignCode = "kanvas-snapshot-901" + ErrGeneratingSnapshotCode = "kanvas-snapshot-902" + ErrHTTPPostRequestCode = "kanvas-snapshot-903" + ErrDecodingAPICode = "kanvas-snapshot-905" + ErrUnexpectedResponseCodeCode = "kanvas-snapshot-906" ) func ErrInvalidChartURI(err error) error { @@ -57,3 +59,12 @@ func ErrDecodingAPI(err error) error { []string{"Ensure the Meshery API response format is correct."}, ) } + +func ErrUnexpectedResponseCode(statusCode int, body string) error { + return errors.New(ErrUnexpectedResponseCodeCode, errors.Alert, + []string{"Received unexpected response code from Meshery API."}, + []string{fmt.Sprintf("Status Code: %d, Body: %s", statusCode, body)}, + []string{"The API returned an unexpected status code."}, + []string{"Check the request details and ensure the Meshery API is functioning correctly."}, + ) +}