diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2a191f7..e8b211c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @hatamiarash7 +* @arash-r1c diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3b92599..3f89c4e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,15 +10,6 @@ updates: reviewers: - "hatamiarash7" - - package-ecosystem: "docker" - directory: "/" - schedule: - interval: "daily" - assignees: - - "hatamiarash7" - reviewers: - - "hatamiarash7" - - package-ecosystem: "gomod" directory: "/" schedule: diff --git a/.github/generator/.gitignore b/.github/generator/.gitignore new file mode 100644 index 0000000..6cfa079 --- /dev/null +++ b/.github/generator/.gitignore @@ -0,0 +1,35 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +# CGO +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +# Other +_testmain.go +*.exe +*.test +*.prof +.DS_Store + +# IDE +.vscode + +# Generator +.openapi-generator-ignore +.openapi-generator +.travis.yml +git_push.sh diff --git a/CODE_OF_CONDUCT.md b/.github/generator/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/generator/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/generator/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/generator/CONTRIBUTING.md diff --git a/LICENSE b/.github/generator/LICENSE.md similarity index 100% rename from LICENSE rename to .github/generator/LICENSE.md diff --git a/.github/generator/README.md b/.github/generator/README.md new file mode 100644 index 0000000..89ee3c2 --- /dev/null +++ b/.github/generator/README.md @@ -0,0 +1,21 @@ +# ArvanCloud CDN Go + +[![Release](https://github.com/arvancloud/cdn-go/actions/workflows/release.yaml/badge.svg)](https://github.com/arvancloud/cdn-go/actions/workflows/release.yaml) [![CodeQL](https://github.com/arvancloud/cdn-go/actions/workflows/codeql.yaml/badge.svg)](https://github.com/arvancloud/cdn-go/actions/workflows/codeql.yaml) ![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/r1cloud/cdn?sort=semver) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/arvancloud/cdn-go) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/arvancloud/cdn-go?display_name=tag&label=version&sort=semver) + +![logo](.github/logo.svg) + +It's a Go library for interacting with the ArvanCloud CDN API. + +## Installation + +```bash +go get github.com/arvancloud/cdn-go +``` + +## Usage + +Check the [HOW-TO.md](docs/HOW-TO.md) file for more information. + +## Contributing + +We welcome contributions from the community. Please report any issues you find in the [Issues page](https://github.com/arvancloud/cdn-go/issues) or send us an email at [cdn@arvancloud.ir](mailto:cdn@arvancloud.ir). diff --git a/SECURITY.md b/.github/generator/SECURITY.md similarity index 100% rename from SECURITY.md rename to .github/generator/SECURITY.md diff --git a/.github/generator/config.yml b/.github/generator/config.yml new file mode 100644 index 0000000..e4425ec --- /dev/null +++ b/.github/generator/config.yml @@ -0,0 +1,3 @@ +packageName: r1cdn +generateInterfaces: false +packageVersion: 1.0.0 diff --git a/.github/generator/template/README.mustache b/.github/generator/template/README.mustache new file mode 100644 index 0000000..b12bd73 --- /dev/null +++ b/.github/generator/template/README.mustache @@ -0,0 +1,224 @@ +# Go API client for {{packageName}} + +Use this documentation to learn how to use the ArvanCloud SDK. + +**API version**: {{appVersion}} + +## Dependencies + +Install the following packages: + +```shell +go get github.com/stretchr/testify/assert +{{#hasOAuthMethods}} +go get golang.org/x/oauth2 +{{/hasOAuthMethods}} +go get golang.org/x/net/context +``` + +## Usage + +Get the package: + +```bash +go get github.com/arvancloud/cdn-go +``` + +Put the package under your project folder and add the following in import: + +```golang +import {{packageName}} "github.com/arvancloud/cdn-go" +``` + +## Configuration of Server URL + +Default configuration comes with `Servers` field that contains server objects as defined in the OpenAPI specification. + +### Select Server Configuration + +For using other server than the one defined on index 0 set context value `sw.ContextServerIndex` of type `int`. + +```golang +ctx := context.WithValue(context.Background(), {{packageName}}.ContextServerIndex, 1) +``` + +### Templated Server URL + +Templated server URL is formatted using default variables from configuration or from context value `sw.ContextServerVariables` of type `map[string]string`. + +```golang +ctx := context.WithValue(context.Background(), {{packageName}}.ContextServerVariables, map[string]string{ + "basePath": "v2", +}) +``` + +Note, enum values are always validated and all unused variables are silently ignored. + +### URLs Configuration per Operation + +Each operation can use different server URL defined using `OperationServers` map in the `Configuration`. +An operation is uniquely identified by `"{classname}Service.{nickname}"` string. +Similar rules for overriding default operation server index and variables applies by using `sw.ContextOperationServerIndices` and `sw.ContextOperationServerVariables` context maps. + +```golang +ctx := context.WithValue(context.Background(), {{packageName}}.ContextOperationServerIndices, map[string]int{ + "{classname}Service.{nickname}": 2, +}) +ctx = context.WithValue(context.Background(), {{packageName}}.ContextOperationServerVariables, map[string]map[string]string{ + "{classname}Service.{nickname}": { + "port": "8443", + }, +}) +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation For Models + +{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} + +## Documentation For Authorization + +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} +### {{{name}}} + +{{#isApiKey}} +- **Type**: API key +- **API key parameter name**: {{{keyParamName}}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} + +Note, each API key must be added to a map of `map[string]APIKey` where the key is: {{keyParamName}} and passed in as the auth context for each request. + +Example + +```golang +auth := context.WithValue( + context.Background(), + sw.ContextAPIKeys, + map[string]sw.APIKey{ + "{{keyParamName}}": {Key: "API_KEY_STRING"}, + }, + ) +r, err := client.Service.Operation(auth, args) +``` + +{{/isApiKey}} +{{#isBasic}} +{{#isBasicBearer}} +- **Type**: HTTP Bearer token authentication + +Example + +```golang +auth := context.WithValue(context.Background(), sw.ContextAccessToken, "BEARER_TOKEN_STRING") +r, err := client.Service.Operation(auth, args) +``` + +{{/isBasicBearer}} +{{#isBasicBasic}} +- **Type**: HTTP basic authentication + +Example + +```golang +auth := context.WithValue(context.Background(), sw.ContextBasicAuth, sw.BasicAuth{ + UserName: "username", + Password: "password", +}) +r, err := client.Service.Operation(auth, args) +``` + +{{/isBasicBasic}} +{{#isHttpSignature}} +- **Type**: HTTP signature authentication + +Example + +```golang + authConfig := client.HttpSignatureAuth{ + KeyId: "my-key-id", + PrivateKeyPath: "rsa.pem", + Passphrase: "my-passphrase", + SigningScheme: sw.HttpSigningSchemeHs2019, + SignedHeaders: []string{ + sw.HttpSignatureParameterRequestTarget, // The special (request-target) parameter expresses the HTTP request target. + sw.HttpSignatureParameterCreated, // Time when request was signed, formatted as a Unix timestamp integer value. + "Host", // The Host request header specifies the domain name of the server, and optionally the TCP port number. + "Date", // The date and time at which the message was originated. + "Content-Type", // The Media type of the body of the request. + "Digest", // A cryptographic digest of the request body. + }, + SigningAlgorithm: sw.HttpSigningAlgorithmRsaPSS, + SignatureMaxValidity: 5 * time.Minute, + } + var authCtx context.Context + var err error + if authCtx, err = authConfig.ContextWithValue(context.Background()); err != nil { + // Process error + } + r, err = client.Service.Operation(auth, args) + +``` +{{/isHttpSignature}} +{{/isBasic}} +{{#isOAuth}} + +- **Type**: OAuth +- **Flow**: {{{flow}}} +- **Authorization URL**: {{{authorizationUrl}}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - **{{{scope}}}**: {{{description}}} +{{/scopes}} + +Example + +```golang +auth := context.WithValue(context.Background(), sw.ContextAccessToken, "ACCESSTOKENSTRING") +r, err := client.Service.Operation(auth, args) +``` + +Or via OAuth2 module to automatically refresh tokens and perform user authentication. + +```golang +import "golang.org/x/oauth2" + +/* Perform OAuth2 round trip request and obtain a token */ + +tokenSource := oauth2cfg.TokenSource(createContext(httpClient), &token) +auth := context.WithValue(oauth2.NoContext, sw.ContextOAuth2, tokenSource) +r, err := client.Service.Operation(auth, args) +``` + +{{/isOAuth}} +{{/authMethods}} + +## Documentation for Utility Methods + +Due to the fact that model structure members are all pointers, this package contains +a number of utility functions to easily obtain pointers to values of basic types. +Each of these functions takes a value of the given basic type and returns a pointer to it: + +* `PtrBool` +* `PtrInt` +* `PtrInt32` +* `PtrInt64` +* `PtrFloat` +* `PtrFloat32` +* `PtrFloat64` +* `PtrString` +* `PtrTime` + +## Author + +{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} +{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/.github/generator/tune.sh b/.github/generator/tune.sh new file mode 100644 index 0000000..64d928c --- /dev/null +++ b/.github/generator/tune.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cat .github/generator/.gitignore >>.gitignore +mv README.md docs/HOW-TO.md +cp .github/generator/*.md . +sed -i 's/GIT_USER_ID/arvancloud/g' go.mod test/*.go docs/*.md +sed -i 's/GIT_REPO_ID/cdn-go/g' go.mod test/*.go docs/*.md +sed -i 's+(docs/+(+g' docs/*.md +sed -i 's+../README.md#+HOW-TO.md#+g' docs/*.md +sed -i 's+(../README.md)+(HOW-TO.md)+g' docs/*.md diff --git a/.github/release.yaml b/.github/release.yaml new file mode 100644 index 0000000..8d542c6 --- /dev/null +++ b/.github/release.yaml @@ -0,0 +1,140 @@ +name: Release + +on: + release: + types: [published] + +jobs: + init: + name: 🚩 Initialize + runs-on: ubuntu-latest + steps: + - name: Cancel previous workflow + uses: styfle/cancel-workflow-action@0.11.0 + with: + access_token: ${{ github.token }} + + build: + name: ⚙️ Build ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ubuntu-20.04 + needs: init + strategy: + fail-fast: false + matrix: + os: [linux, darwin] + arch: [amd64, arm64] + include: + - { os: linux, arch: 386 } + - { os: windows, arch: 386 } + - { os: windows, arch: amd64 } + steps: + - name: Setup Golang + uses: actions/setup-go@v4 + with: + go-version: "1.20" + + - name: Checkout + uses: actions/checkout@v3 + + - name: Get repository info + uses: gacts/github-slug@v1 + id: slug + + - name: Generate builder values + id: values + run: echo "::set-output name=binary-name::cdn-${{ matrix.os }}-${{ matrix.arch }}`[ ${{ matrix.os }} = 'windows' ] && echo '.exe'`" + + - name: Install Go dependencies + run: go mod download + + - name: Build + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + CGO_ENABLED: 0 + LDFLAGS: -s -w -X github.com/arvancloud/cdn-go/internal/pkg/version.version=${{ steps.slug.outputs.version }} + run: go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" cmd/*.go + + - name: Test + run: go test -v -race ./... -json > TestResults.json + + - name: Upload test results + uses: actions/upload-artifact@v3 + with: + name: Test-results + path: TestResults.json + + - name: Run UPX + uses: crazy-max/ghaction-upx@v2 + with: + version: latest + files: | + ./${{ steps.values.outputs.binary-name }} + args: --best --lzma + + - name: Upload binaries + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ github.token }} + file: ${{ steps.values.outputs.binary-name }} + asset_name: ${{ steps.values.outputs.binary-name }} + tag: ${{ github.ref }} + + docker: + name: 🐳 Build Docker image + runs-on: ubuntu-20.04 + needs: init + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get repository info + uses: gacts/github-slug@v1 + id: slug + + - name: Setup QEMU + uses: docker/setup-qemu-action@v2 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Dockerhub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_LOGIN }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get Current Date + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + + - name: Build & Push image + uses: docker/build-push-action@v4 + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7 + build-args: | + APP_VERSION=${{ steps.slug.outputs.version }}" + DATE_CREATED=${{ steps.date.outputs.date }} + tags: | + r1cloud/cdn:${{ steps.slug.outputs.version }} + r1cloud/cdn:latest + + - name: Run Trivy + uses: aquasecurity/trivy-action@master + with: + image-ref: r1cloud/cdn:latest + exit-code: "0" + ignore-unfixed: true + vuln-type: "os,library" + severity: "CRITICAL,HIGH" + format: "template" + template: "@/contrib/sarif.tpl" + output: "trivy-results.sarif" + + - name: Upload Trivy scan results + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results.sarif" diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml deleted file mode 100644 index 0e555db..0000000 --- a/.github/workflows/codeql.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: CodeQL - -on: - push: - branches: - - main - paths: - - cmd - - pkg - - go.mod - - go.sum - - .github/workflows/codeql.yaml - pull_request: - branches: - - main - paths: - - cmd - - pkg - - go.mod - - go.sum - - .github/workflows/codeql.yaml - types: [opened, reopened, synchronize, ready_for_review] - workflow_dispatch: - -jobs: - analyze: - name: Analyze - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: "go" - - - name: Build - run: make cli - - - name: Test - run: go test -v -race ./... - - - name: CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:go" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8d542c6..faa7fe2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,51 +12,66 @@ jobs: - name: Cancel previous workflow uses: styfle/cancel-workflow-action@0.11.0 with: - access_token: ${{ github.token }} + access_token: ${{ secrets.PAT }} build: - name: ⚙️ Build ${{ matrix.os }} - ${{ matrix.arch }} + name: ✏️ Generate runs-on: ubuntu-20.04 needs: init - strategy: - fail-fast: false - matrix: - os: [linux, darwin] - arch: [amd64, arm64] - include: - - { os: linux, arch: 386 } - - { os: windows, arch: 386 } - - { os: windows, arch: amd64 } + permissions: + contents: write steps: - - name: Setup Golang - uses: actions/setup-go@v4 - with: - go-version: "1.20" - - name: Checkout uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.PAT }} - - name: Get repository info - uses: gacts/github-slug@v1 - id: slug + - name: Get version + run: echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV - - name: Generate builder values - id: values - run: echo "::set-output name=binary-name::cdn-${{ matrix.os }}-${{ matrix.arch }}`[ ${{ matrix.os }} = 'windows' ] && echo '.exe'`" + - name: Cleanup + run: rm -rf api docs test *.go go.* .gitignore - - name: Install Go dependencies - run: go mod download + - name: OpenAPI Generator + uses: hatamiarash7/openapi-generator@main + with: + generator: go + generator-tag: v6.6.0 + config-file: .github/generator/config.yml + openapi-url: https://www.arvancloud.ir/api-docs/cdn-4.0.yml + command-args: "--http-user-agent r1c-cdn-sdk" + output-dir: "/github/workspace" + template-dir: .github/generator/template/ + + - name: Update output + run: bash .github/generator/tune.sh + + - name: Get version + id: version + uses: jbutcher5/read-yaml@main + with: + file: "./api/openapi.yaml" + key-path: '["info", "version"]' + + - name: Setup Golang + uses: actions/setup-go@v4 + with: + go-version: "1.18" - name: Build env: - GOOS: ${{ matrix.os }} - GOARCH: ${{ matrix.arch }} + GOOS: linux + GOARCH: amd64 CGO_ENABLED: 0 - LDFLAGS: -s -w -X github.com/arvancloud/cdn-go/internal/pkg/version.version=${{ steps.slug.outputs.version }} - run: go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" cmd/*.go + LDFLAGS: -s -w + run: go build -trimpath -ldflags "$LDFLAGS" -o "./cdn" . - name: Test - run: go test -v -race ./... -json > TestResults.json + run: | + go get github.com/stretchr/testify/assert + go get golang.org/x/net/context + go test -v -race ./... -json > TestResults.json - name: Upload test results uses: actions/upload-artifact@v3 @@ -64,77 +79,14 @@ jobs: name: Test-results path: TestResults.json - - name: Run UPX - uses: crazy-max/ghaction-upx@v2 - with: - version: latest - files: | - ./${{ steps.values.outputs.binary-name }} - args: --best --lzma - - - name: Upload binaries - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ github.token }} - file: ${{ steps.values.outputs.binary-name }} - asset_name: ${{ steps.values.outputs.binary-name }} - tag: ${{ github.ref }} - - docker: - name: 🐳 Build Docker image - runs-on: ubuntu-20.04 - needs: init - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Get repository info - uses: gacts/github-slug@v1 - id: slug - - - name: Setup QEMU - uses: docker/setup-qemu-action@v2 - - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Dockerhub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_LOGIN }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get Current Date - id: date - run: echo "::set-output name=date::$(date +'%Y-%m-%d')" - - - name: Build & Push image - uses: docker/build-push-action@v4 - with: - context: . - file: Dockerfile - push: true - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7 - build-args: | - APP_VERSION=${{ steps.slug.outputs.version }}" - DATE_CREATED=${{ steps.date.outputs.date }} - tags: | - r1cloud/cdn:${{ steps.slug.outputs.version }} - r1cloud/cdn:latest - - - name: Run Trivy - uses: aquasecurity/trivy-action@master - with: - image-ref: r1cloud/cdn:latest - exit-code: "0" - ignore-unfixed: true - vuln-type: "os,library" - severity: "CRITICAL,HIGH" - format: "template" - template: "@/contrib/sarif.tpl" - output: "trivy-results.sarif" + - name: Cleanup + run: rm -rf cdn TestResults.json - - name: Upload Trivy scan results - uses: github/codeql-action/upload-sarif@v2 + - name: Commit & Push changes + uses: stefanzweifel/git-auto-commit-action@v4 with: - sarif_file: "trivy-results.sarif" + branch: main + commit_message: "SDK ${{ env.APP_VERSION }} - API v${{ steps.version.outputs.data }}" + commit_user_name: "ArvanCloud" + commit_user_email: "cdn.sre@arvancloud.ir" + commit_author: Arash diff --git a/.gitignore b/.gitignore deleted file mode 100644 index c15c17a..0000000 --- a/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Binary folder -/bin - -# Test binary, built with `go test -c` -*.test -junit*.xml - -# Coverage files -coverage*[.html, .xml] -*.out - -# Lint -checkstyle-report.xml -yamllint-checkstyle.xml - -# Log file -*.log - -# IDEs -/.vscode -/.idea - -# Vim swap -[._]*.s[a-v][a-z] -[._]*.sw[a-p] -[._]s[a-rt-v][a-z] -[._]ss[a-gi-z] -[._]sw[a-p] - -# Vim session -Session.vim - -# Vim temporary -.netrwhist -*~ - -# Vim persistent undo -[._]*.un~ - -# vendor files -vendor/* - -# Others diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 66584e1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "cSpell.words": [ - "ANAME", - "backoff", - "gcli", - "gookit", - "iodef", - "issuewild", - "Roadmap", - "stretchr", - "TLSA", - "unmarshalling" - ] -} diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 91176f8..0000000 --- a/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -##################################### Build ##################################### - -FROM --platform=$BUILDPLATFORM golang:1.20-alpine as builder - -ARG APP_VERSION="undefined@docker" - -WORKDIR /src - -COPY cmd cmd -COPY pkg pkg -COPY go.* . - -ENV LDFLAGS="-s -w -X github.com/arvancloud/cdn-go/internal/pkg/version.version=$APP_VERSION" -ENV GO111MODULE=on -ARG TARGETOS TARGETARCH -ENV GOOS $TARGETOS -ENV GOARCH $TARGETARCH - -RUN set -x \ - && go version \ - && CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o /src/cdn-uncompress cmd/*.go \ - && apk add -U --no-cache ca-certificates - -##################################### Compression ##################################### - -FROM hatamiarash7/upx:latest as upx - -COPY --from=builder /src / - -RUN upx --best --lzma -o /cdn /cdn-uncompress - -######################################## Final ######################################## - -FROM scratch - -ARG APP_VERSION="undefined@docker" -ARG DATE_CREATED - -LABEL \ - org.opencontainers.image.title="cdn" \ - org.opencontainers.image.description="Docker image for ArvanCloud CDN API " \ - org.opencontainers.image.url="https://github.com/arvancloud/cdn-go" \ - org.opencontainers.image.source="https://github.com/arvancloud/cdn-go" \ - org.opencontainers.image.vendor="arvancloud" \ - org.opencontainers.image.author="arvancloud" \ - org.opencontainers.version="$APP_VERSION" \ - org.opencontainers.image.created="$DATE_CREATED" \ - org.opencontainers.image.licenses="MIT" - -COPY --from=upx /cdn /cdn - -COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ - -ENTRYPOINT ["/cdn"] diff --git a/Makefile b/Makefile deleted file mode 100644 index bc5617f..0000000 --- a/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -GOCMD=go -GOTEST=$(GOCMD) test -EXPORT_RESULT?=FALSE -SOURCES=$(shell find . -name '*.go' -not -name '*_test.go' -not -name "main.go") -BIN_DIR:=bin - -.PHONY: clean pre cli goconvey test coverage help -.DEFAULT_GOAL := help - -##################################### Binary ##################################### - -clean: ## Clean the bin directory - rm -rf $(BIN_DIR) - rm -f ./checkstyle-report.xml checkstyle-report.xml yamllint-checkstyle.xml - -pre: clean ## Create the bin directory - mkdir -p $(BIN_DIR) - -cli: pre $(BIN_DIR)/cdn ## Build the CLI binary - -$(BIN_DIR)/%: cmd $(SOURCES) - CGO_ENABLED=0 $(GOCMD) build -ldflags="-s -w" -o $@ $ coverage.xml - rm -f coverage.out -endif - -##################################### Help ##################################### - -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md deleted file mode 100644 index 93a5cdb..0000000 --- a/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# ArvanCloud CDN Go - -[![Release](https://github.com/arvancloud/cdn-go/actions/workflows/release.yaml/badge.svg)](https://github.com/arvancloud/cdn-go/actions/workflows/release.yaml) [![CodeQL](https://github.com/arvancloud/cdn-go/actions/workflows/codeql.yaml/badge.svg)](https://github.com/arvancloud/cdn-go/actions/workflows/codeql.yaml) ![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/r1cloud/cdn?sort=semver) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/arvancloud/cdn-go) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/arvancloud/cdn-go?display_name=tag&label=version&sort=semver) - -![logo](.github/logo.svg) - -It's a Go library for interacting with the ArvanCloud CDN API. - -> **Note**: This project is under active development and may have problems and shortcomings. - -## Installation - -```bash -go get github.com/arvancloud/cdn-go -``` - -## Usage - -```go -package main - -import ( - "context" - "fmt" - "log" - "os" - - arvancloud "github.com/arvancloud/cdn-go/pkg" -) - -func main() { - api, err := arvancloud.New( - os.Getenv("ARVANCLOUD_API_KEY"), - arvancloud.Debug(false), - ) - if err != nil { - log.Fatal(err) - } - - ctx := context.Background() - - resource := arvancloud.Resource{ - Domain: "test.ir", - } - - record := arvancloud.CreateDNSRecordParams{ - Type: "A", - Name: "@", - Value: []arvancloud.DNSRecord_Value_A{ - { - IP: "1.1.1.1", - }, - }, - TTL: 120, - UpstreamHTTPS: "https", - IPFilterMode: arvancloud.DNSRecord_IPFilterMode{ - Count: "single", - Order: "none", - GeoFilter: "none", - }, - } - - u, err := api.CreateDNSRecord(ctx, resource, record) - if err != nil { - log.Fatal(err.Error()) - } - - fmt.Printf("%+v", u) -} -``` - -## Roadmap - -- [ ] Products - - [x] DNS - - [ ] Firewall - - [ ] WAF - - [ ] DDoS - - [ ] Cache - - [ ] Load Balancer - - [ ] Page Rule - - [ ] Acceleration - - [ ] Custom Pages - - [ ] Redirect - - [ ] Log Forwarder - - [ ] L4 Proxy - - [ ] Rate Limit -- [x] Package - - [x] CLI - - [x] Official Release - - [x] CI/CD - -## Contributing - -We welcome contributions from the community. Please consider that this project is under active development as we expand it to cover the CDN API. - -Please report any issues you find in the [Issues page](https://github.com/arvancloud/cdn-go/issues) or send us an email at [cdn@arvancloud.ir](mailto:cdn@arvancloud.ir). diff --git a/cmd/dns.go b/cmd/dns.go deleted file mode 100644 index 18edfdc..0000000 --- a/cmd/dns.go +++ /dev/null @@ -1,421 +0,0 @@ -package main - -import ( - "context" - "log" - "strconv" - - arvancloud "github.com/arvancloud/cdn-go/pkg" - "github.com/gookit/gcli/v3/interact" -) - -// List of functions for every DNS record type -var funcs = map[string]func() interface{}{ - "A": createDNSRecord_A, - "AAAA": createDNSRecord_AAAA, - "MX": createDNSRecord_MX, - "NS": createDNSRecord_NS, - "SRV": createDNSRecord_SRV, - "TXT": createDNSRecord_TXT, - "SPF": createDNSRecord_SPF, - "DKIM": createDNSRecord_DKIM, - "ANAME": createDNSRecord_ANAME, - "CNAME": createDNSRecord_CNAME, - "PTR": createDNSRecord_PTR, - "TLSA": createDNSRecord_TLSA, - "CAA": createDNSRecord_CAA, -} - -// GetDNSRecord will return a single DNS record -func GetDNSRecord(ctx context.Context, api *arvancloud.API, domain string, id string) { - resource := arvancloud.Resource{ - Domain: domain, - } - - u, err := api.GetDNSRecord(ctx, resource, id) - if err != nil { - log.Fatal(err.Error()) - } - - arvancloud.PrettyPrint(u) -} - -// ListDNSRecords will list a part of DNS records -func ListDNSRecords(ctx context.Context, api *arvancloud.API, domain string, page int, perPage int) { - param := arvancloud.ListDNSRecordsParams{ - Type: "a", - Page: page, - PerPage: perPage, - } - - resource := arvancloud.Resource{ - Domain: domain, - } - - u, err := api.ListDNSRecords(ctx, resource, param) - if err != nil { - log.Fatal(err.Error()) - } - - arvancloud.PrettyPrint(u) -} - -// DeleteDNSRecord will delete a DNS record -func DeleteDNSRecord(ctx context.Context, api *arvancloud.API, domain string, id string) { - resource := arvancloud.Resource{ - Domain: domain, - } - - u, err := api.DeleteDNSRecord(ctx, resource, id) - if err != nil { - log.Fatal(err.Error()) - } - - arvancloud.PrettyPrint(u) -} - -// CreateDNSRecord will create a DNS record -func CreateDNSRecord(ctx context.Context, api *arvancloud.API, domain string, record *arvancloud.CreateDNSRecordParams) { - resource := arvancloud.Resource{ - Domain: domain, - } - - u, err := api.CreateDNSRecord(ctx, resource, *record) - if err != nil { - log.Fatal(err.Error()) - } - - arvancloud.PrettyPrint(u) -} - -// createDNSRecordParams will configure the parameters for creating a DNS record -func createDNSRecordParams(params *arvancloud.CreateDNSRecordParams) { - recordName, _ := interact.ReadInput("Record name: ") - params.Name = recordName - - recordType := interact.SelectOne( - "Record type", - []string{ - "A", - "AAAA", - "MX", - "NS", - "SRV", - "TXT", - "SPF", - "DKIM", - "ANAME", - "CNAME", - "PTR", - "TLSA", - "CAA", - }, - "", - false, - ) - params.Type = recordType - - ttl, _ := interact.ReadInput("Record TTL: ") - recordTTL, err := strconv.Atoi(ttl) - if err != nil { - log.Fatal(err.Error()) - } - params.TTL = recordTTL - - cloud := interact.SelectOne( - "Record Cloud:", - []string{ - "True", - "False", - }, - "", - false, - ) - recordCloud, err := strconv.ParseBool(cloud) - if err != nil { - log.Fatal(err.Error()) - } - params.Cloud = recordCloud - - recordUpstreamHTTPS := interact.SelectOne( - "Record Upstream HTTPS:", - []string{ - "default", - "auto", - "http", - "https", - }, - "", - false, - ) - params.UpstreamHTTPS = recordUpstreamHTTPS - - // Call function based on record type - if f, ok := funcs[recordType]; ok { - params.Value = f() - } else { - log.Fatal("Unknown record type:", recordType) - } - - params.IPFilterMode = createDNSRecord_IPFilterMode() -} - -func createDNSRecord_A() interface{} { - ip, _ := interact.ReadInput("Record IP: ") - - p, _ := interact.ReadInput("Record Port: ") - port, err := strconv.Atoi(p) - if err != nil { - log.Fatal(err.Error()) - } - - w, _ := interact.ReadInput("Record Weight: ") - weight, err := strconv.Atoi(w) - if err != nil { - log.Fatal(err.Error()) - } - - country, _ := interact.ReadInput("Record Country: ") - - return arvancloud.DNSRecord_Value_A{ - IP: ip, - Port: port, - Weight: weight, - Country: country, - } -} - -func createDNSRecord_AAAA() interface{} { - ip, _ := interact.ReadInput("Record IP: ") - - p, _ := interact.ReadInput("Record Port: ") - port, err := strconv.Atoi(p) - if err != nil { - log.Fatal(err.Error()) - } - - w, _ := interact.ReadInput("Record Weight: ") - weight, err := strconv.Atoi(w) - if err != nil { - log.Fatal(err.Error()) - } - - country, _ := interact.ReadInput("Record Country: ") - - return arvancloud.DNSRecord_Value_AAAA{ - IP: ip, - Port: port, - Weight: weight, - Country: country, - } -} - -func createDNSRecord_MX() interface{} { - host, _ := interact.ReadInput("Record Host: ") - - p, _ := interact.ReadInput("Record Priority: ") - priority, err := strconv.Atoi(p) - if err != nil { - log.Fatal(err.Error()) - } - - return arvancloud.DNSRecord_Value_MX{ - Host: host, - Priority: priority, - } -} - -func createDNSRecord_NS() interface{} { - host, _ := interact.ReadInput("Record Host: ") - - return arvancloud.DNSRecord_Value_NS{ - Host: host, - } -} - -func createDNSRecord_SRV() interface{} { - target, _ := interact.ReadInput("Record Target: ") - - p, _ := interact.ReadInput("Record Port: ") - port, err := strconv.Atoi(p) - if err != nil { - log.Fatal(err.Error()) - } - - w, _ := interact.ReadInput("Record Weight: ") - weight, err := strconv.Atoi(w) - if err != nil { - log.Fatal(err.Error()) - } - - p, _ = interact.ReadInput("Record Priority: ") - priority, err := strconv.Atoi(p) - if err != nil { - log.Fatal(err.Error()) - } - - return arvancloud.DNSRecord_Value_SRV{ - Target: target, - Port: port, - Weight: weight, - Priority: priority, - } -} - -func createDNSRecord_TXT() interface{} { - text, _ := interact.ReadInput("Record Text: ") - - return arvancloud.DNSRecord_Value_TXT{ - Text: text, - } -} - -func createDNSRecord_SPF() interface{} { - text, _ := interact.ReadInput("Record Text: ") - - return arvancloud.DNSRecord_Value_SPF{ - Text: text, - } -} - -func createDNSRecord_DKIM() interface{} { - text, _ := interact.ReadInput("Record Text: ") - - return arvancloud.DNSRecord_Value_DKIM{ - Text: text, - } -} - -func createDNSRecord_ANAME() interface{} { - location, _ := interact.ReadInput("Record Location: ") - - host_header := interact.SelectOne( - "Record Host header:", - []string{ - "source", - "dest", - }, - "", - false, - ) - - p, _ := interact.ReadInput("Record Port: ") - port, err := strconv.Atoi(p) - if err != nil { - log.Fatal(err.Error()) - } - - return arvancloud.DNSRecord_Value_ANAME{ - Location: location, - HostHeader: host_header, - Port: port, - } -} - -func createDNSRecord_CNAME() interface{} { - host, _ := interact.ReadInput("Record Host: ") - - host_header := interact.SelectOne( - "Record Host header:", - []string{ - "source", - "dest", - }, - "", - false, - ) - - p, _ := interact.ReadInput("Record Port: ") - port, err := strconv.Atoi(p) - if err != nil { - log.Fatal(err.Error()) - } - - return arvancloud.DNSRecord_Value_CNAME{ - Host: host, - HostHeader: host_header, - Port: port, - } -} - -func createDNSRecord_PTR() interface{} { - domain, _ := interact.ReadInput("Record Domain: ") - - return arvancloud.DNSRecord_Value_PTR{ - Domain: domain, - } -} - -func createDNSRecord_TLSA() interface{} { - usage, _ := interact.ReadInput("Record Usage: ") - selector, _ := interact.ReadInput("Record Selector: ") - matching_type, _ := interact.ReadInput("Record Matching type: ") - certificate, _ := interact.ReadInput("Record Certificate: ") - - return arvancloud.DNSRecord_Value_TLSA{ - Usage: usage, - Selector: selector, - MatchingType: matching_type, - Certificate: certificate, - } -} - -func createDNSRecord_CAA() interface{} { - value, _ := interact.ReadInput("Record Value: ") - - tag := interact.SelectOne( - "Record Tag:", - []string{ - "issuewild", - "issue", - "iodef", - }, - "", - false, - ) - - return arvancloud.DNSRecord_Value_CAA{ - Value: value, - Tag: tag, - } -} - -func createDNSRecord_IPFilterMode() interface{} { - count := interact.SelectOne( - "Record Count:", - []string{ - "single", - "multi", - }, - "", - false, - ) - - order := interact.SelectOne( - "Record Order:", - []string{ - "none", - "weighted", - "rr", - }, - "", - false, - ) - - geo_filter := interact.SelectOne( - "Record Geo filter:", - []string{ - "none", - "location", - "country", - }, - "", - false, - ) - - return arvancloud.DNSRecord_IPFilterMode{ - Count: count, - Order: order, - GeoFilter: geo_filter, - } -} diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index 3b4a263..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,146 +0,0 @@ -package main - -import ( - "context" - "log" - "os" - - arvancloud "github.com/arvancloud/cdn-go/pkg" - "github.com/gookit/gcli/v3" -) - -var ( - ctx = context.Background() -) - -func main() { - // Create a new instance of the API client - api, err := arvancloud.New( - os.Getenv("ARVANCLOUD_API_KEY"), - arvancloud.Debug(false), - ) - if err != nil { - log.Fatal(err) - } - - // Create a new instance of the CLI application - app := gcli.NewApp() - app.Version = arvancloud.VERSION - app.Desc = "ArvanCloud CDN" - - // Configure CLI arguments - configureArgs(app, api) - - app.Run(nil) -} - -func configureArgs(app *gcli.App, api *arvancloud.API) { - domainArg := &gcli.Argument{ - Name: "domain", - Desc: "Your domain, is required", - Required: true, - } - - // Create DNS record - - createDNSRecord := &gcli.Command{ - Name: "create", - Desc: "Create a single DNS record", - Aliases: []string{"c"}, - Func: func(cmd *gcli.Command, _ []string) error { - var params *arvancloud.CreateDNSRecordParams - params = new(arvancloud.CreateDNSRecordParams) - - createDNSRecordParams(params) - - CreateDNSRecord( - ctx, - api, - cmd.Arg("domain").String(), - params, - ) - - return nil - }, - Config: func(cmd *gcli.Command) { - cmd.AddArgument(domainArg) - }, - } - - // Get DNS record - - getDNSRecord := &gcli.Command{ - Name: "get", - Desc: "Get a single DNS record", - Aliases: []string{"g"}, - Examples: `{$fullCmd} domain.ir 4j6eff36-8e7b-4c12-a7d1-20804e839a67`, - Func: func(cmd *gcli.Command, _ []string) error { - GetDNSRecord( - ctx, - api, - cmd.Arg("domain").String(), - cmd.Arg("id").String(), - ) - return nil - }, - Config: func(cmd *gcli.Command) { - cmd.AddArgument(domainArg) - cmd.AddArg("id", "The id of DNS record, is required", true) - }, - } - - // List DNS records - - listDNSRecord := &gcli.Command{ - Name: "list", - Desc: "List DNS records", - Aliases: []string{"l", "ls"}, - Examples: `{$fullCmd} domain.ir -{$fullCmd} domain.ir 2 -{$fullCmd} domain.ir 1 2`, - Func: func(cmd *gcli.Command, _ []string) error { - ListDNSRecords( - ctx, - api, - cmd.Arg("domain").String(), - cmd.Arg("page").Int(), - cmd.Arg("per-page").Int(), - ) - return nil - }, - Config: func(cmd *gcli.Command) { - cmd.AddArgument(domainArg) - cmd.AddArg("page", "The page number for pagination", false) - cmd.AddArg("per-page", "Number of records in each page", false) - }, - } - - // Delete DNS record - - deleteDNSRecord := &gcli.Command{ - Name: "delete", - Desc: "Delete a single DNS record", - Aliases: []string{"d", "del"}, - Examples: `{$fullCmd} domain.ir 4j6eff36-8e7b-4c12-a7d1-20804e839a67`, - Func: func(cmd *gcli.Command, _ []string) error { - DeleteDNSRecord( - ctx, - api, - cmd.Arg("domain").String(), - cmd.Arg("id").String(), - ) - return nil - }, - Config: func(cmd *gcli.Command) { - cmd.AddArgument(domainArg) - cmd.AddArg("id", "The id of DNS record, is required", true) - }, - } - - app.Add( - createDNSRecord, - getDNSRecord, - listDNSRecord, - deleteDNSRecord, - ) -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 6a1634c..0000000 --- a/go.mod +++ /dev/null @@ -1,28 +0,0 @@ -module github.com/arvancloud/cdn-go - -go 1.20 - -require ( - github.com/google/go-querystring v1.1.0 - github.com/gookit/gcli/v3 v3.2.1 - github.com/stretchr/testify v1.8.4 - golang.org/x/time v0.3.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/gookit/color v1.5.2 // indirect - github.com/gookit/goutil v0.6.6 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index a9378c0..0000000 --- a/go.sum +++ /dev/null @@ -1,58 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= -github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= -github.com/gookit/gcli/v3 v3.2.1 h1:xiCJk8xOl3vB04mOIzn3aSAMttCtQpuLwaznsV9cfb4= -github.com/gookit/gcli/v3 v3.2.1/go.mod h1:hfMxHR72+JNFN5nMOZPau2e4G1z57pMfIPq+dZDaYkI= -github.com/gookit/goutil v0.6.6 h1:XdvnPocHpKDXA+eykfc/F846Y1V2Vyo3+cV8rfliG90= -github.com/gookit/goutil v0.6.6/go.mod h1:D++7kbQd/6vECyYTxB5tq6AKDIG9ZYwZNhubWJvN9dw= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/arvancloud.go b/pkg/arvancloud.go deleted file mode 100644 index 13b1ae5..0000000 --- a/pkg/arvancloud.go +++ /dev/null @@ -1,255 +0,0 @@ -package arvancloud - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "math" - "net/http" - "net/http/httputil" - "regexp" - "strings" - "time" - - "golang.org/x/time/rate" -) - -type API struct { - APIKey string - APIEmail string - BaseURL string - UserAgent string - headers http.Header - httpClient *http.Client - rateLimiter *rate.Limiter - retryPolicy RetryPolicy - logger Logger - Debug bool -} - -type RetryPolicy struct { - MaxRetries int - MinRetryDelay time.Duration - MaxRetryDelay time.Duration -} - -type Logger interface { - Printf(format string, v ...interface{}) -} - -// New creates a new instance of the API client with the given API token and options. -func New(token string, opts ...Option) (*API, error) { - if token == "" { - return nil, errors.New(errEmptyAPIToken) - } - - api, err := newClient(opts...) - if err != nil { - return nil, err - } - - api.APIKey = token - - return api, nil -} - -func newClient(opts ...Option) (*API, error) { - // Create a logger that discards all log output - silentLogger := log.New(io.Discard, "", log.LstdFlags) - - // Create a new API object with default values - api := &API{ - BaseURL: fmt.Sprintf("%s://%s%s", SCHEME, HOST_NAME, BASE_PATH), - UserAgent: UA + "/" + VERSION, - headers: make(http.Header), - rateLimiter: rate.NewLimiter(rate.Limit(4), 1), - retryPolicy: RetryPolicy{ - MaxRetries: 3, - MinRetryDelay: 1 * time.Second, - MaxRetryDelay: 30 * time.Second, - }, - logger: silentLogger, - } - - // Parse the options and apply them to the API object - err := api.parseOptions(opts...) - if err != nil { - return nil, fmt.Errorf("options parsing failed: %w", err) - } - - // Create a new HTTP client using the default client - api.httpClient = http.DefaultClient - - return api, nil -} - -func (api *API) makeRequest(ctx context.Context, method, uri string, record interface{}) ([]byte, error) { - var err error - var resp *http.Response - var respErr error - var respBody []byte - - for i := 0; i <= api.retryPolicy.MaxRetries; i++ { - var reqBody io.Reader - - if record != nil { - if r, ok := record.(io.Reader); ok { - reqBody = r - } else if paramBytes, ok := record.([]byte); ok { - reqBody = bytes.NewReader(paramBytes) - } else { - var jsonBody []byte - jsonBody, err = json.Marshal(record) - - if err != nil { - return nil, fmt.Errorf("error marshalling record data to JSON: %w", err) - } - - reqBody = bytes.NewReader(jsonBody) - } - } - - if i > 0 { - sleepDuration := time.Duration(math.Pow(2, float64(i-1)) * float64(api.retryPolicy.MinRetryDelay)) - - if sleepDuration > api.retryPolicy.MaxRetryDelay { - sleepDuration = api.retryPolicy.MaxRetryDelay - } - - api.logger.Printf("Sleeping %s before retry attempt number %d for request %s %s", sleepDuration.String(), i, method, uri) - - select { - case <-time.After(sleepDuration): - case <-ctx.Done(): - return nil, fmt.Errorf("operation aborted during backoff: %w", ctx.Err()) - } - } - - err = api.rateLimiter.Wait(ctx) - if err != nil { - return nil, fmt.Errorf("error caused by request rate limiting: %w", err) - } - - resp, respErr = api.request(ctx, method, uri, reqBody) - - // short circuit processing on context timeouts - if respErr != nil && errors.Is(respErr, context.DeadlineExceeded) { - return nil, respErr - } - - // retry if the server is rate limiting us or if it failed - // assumes server operations are rolled back on failure - if respErr != nil || resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 { - if resp != nil && resp.StatusCode == http.StatusTooManyRequests { - respErr = errors.New("exceeded available rate limit retries") - } - - if respErr == nil { - respErr = fmt.Errorf("received %s response (HTTP %d), please try again later", strings.ToLower(http.StatusText(resp.StatusCode)), resp.StatusCode) - } - continue - } else { - respBody, err = io.ReadAll(resp.Body) - defer resp.Body.Close() - if err != nil { - return nil, fmt.Errorf("could not read response body: %w", err) - } - - break - } - } - - // still had an error after all retries - if respErr != nil { - return nil, respErr - } - - if resp.StatusCode >= http.StatusBadRequest { - if resp.StatusCode >= http.StatusInternalServerError { - - return nil, &ServiceError{arvancloudError: &Error{ - Code: resp.StatusCode, - Message: errInternalServiceError, - }} - } - - errBody := &CreateDNSRecord_Response{} - err = json.Unmarshal(respBody, &errBody) - if err != nil { - - return nil, fmt.Errorf(errUnmarshalErrorBody+": %w", err) - } - - err := &Error{ - Code: resp.StatusCode, - Message: errBody.Message, - Errors: errBody.Errors, - Type: ErrorTypeRequest, - } - - return nil, &RequestError{arvancloudError: err} - } - - return respBody, nil -} - -func (api *API) request(ctx context.Context, method, uri string, reqBody io.Reader) (*http.Response, error) { - req, err := http.NewRequestWithContext(ctx, method, api.BaseURL+uri, reqBody) - if err != nil { - return nil, fmt.Errorf("HTTP request creation failed: %w", err) - } - - combinedHeaders := make(http.Header) - copyHeader(combinedHeaders, api.headers) - req.Header = combinedHeaders - - req.Header.Set("Authorization", api.APIKey) - req.Header.Set("User-Agent", api.UserAgent) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - - if api.Debug { - dump, err := httputil.DumpRequestOut(req, true) - if err != nil { - return nil, err - } - - // Strip out sensitive information - sensitiveKeys := []string{api.APIKey} - for _, key := range sensitiveKeys { - if key != "" { - valueRegex := regexp.MustCompile(fmt.Sprintf("(?m)%s", key)) - dump = valueRegex.ReplaceAll(dump, []byte("[redacted]")) - } - } - log.Printf("\n%s", string(dump)) - } - - resp, err := api.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("HTTP request failed: %w", err) - } - - if api.Debug { - dump, err := httputil.DumpResponse(resp, true) - if err != nil { - return resp, err - } - log.Printf("\n%s", string(dump)) - } - - return resp, nil -} - -// copyHeader copies all header fields and their values from the source header to the target header. -func copyHeader(target, source http.Header) { - // Iterate over all header fields in the source header. - for k, vs := range source { - // Copy the header field and its values to the target header. - target[k] = vs - } -} diff --git a/pkg/arvancloud_test.go b/pkg/arvancloud_test.go deleted file mode 100644 index a0d0121..0000000 --- a/pkg/arvancloud_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package arvancloud - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - // HTTP request multiplexer - mux *http.ServeMux - - // API client for test - client *API - - // HTTP server used for mocking - server *httptest.Server -) - -func setup(opts ...Option) { - // Create a test server - mux = http.NewServeMux() - server = httptest.NewServer(mux) - - // Disable rate limits and retries for test purpose - opts = append([]Option{UsingRateLimit(100000), UsingRetryPolicy(0, 0, 0)}, opts...) - - // Configure client - client, _ = New("deadbeef", opts...) - client.BaseURL = server.URL -} - -func teardown() { - server.Close() -} - -func TestHeaders(t *testing.T) { - // Default - setup() - mux.HandleFunc("/domains/"+testDomain+"/dns-records", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) - assert.Equal(t, "application/json", r.Header.Get("Content-Type")) - }) - teardown() - - // Override defaults - headers := make(http.Header) - headers.Set("Content-Type", "application/graphql") - headers.Add("H1", "V1") - headers.Add("H2", "V2") - setup(Headers(headers)) - mux.HandleFunc("/domains/"+testDomain+"/dns-records", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) - assert.Equal(t, "application/graphql", r.Header.Get("Content-Type")) - assert.Equal(t, "V1", r.Header.Get("H1")) - assert.Equal(t, "V2", r.Header.Get("H2")) - }) - teardown() - - // Authentication - setup() - client, err := New("TEST_TOKEN") - assert.NoError(t, err) - client.BaseURL = server.URL - mux.HandleFunc("/domains/"+testDomain+"/dns-records", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) - assert.Equal(t, "Bearer TEST_TOKEN", r.Header.Get("Authorization")) - assert.Equal(t, "application/json", r.Header.Get("Content-Type")) - }) - teardown() -} diff --git a/pkg/consts.go b/pkg/consts.go deleted file mode 100644 index 85fc94b..0000000 --- a/pkg/consts.go +++ /dev/null @@ -1,6 +0,0 @@ -package arvancloud - -const ( - // Testing - testDomain = "test.ir" -) diff --git a/pkg/dns.go b/pkg/dns.go deleted file mode 100644 index f4bb945..0000000 --- a/pkg/dns.go +++ /dev/null @@ -1,134 +0,0 @@ -package arvancloud - -import ( - "context" - "encoding/json" - "fmt" - "net/http" -) - -// CreateDNSRecord will create a DNS record -// ? Documentation: https://www.arvancloud.ir/api/cdn/4.0#tag/DNS-Management/operation/dns_records.store -func (api *API) CreateDNSRecord(ctx context.Context, resource Resource, record CreateDNSRecordParams) (res *CreateDNSRecord_Response, err error) { - if resource.Domain == "" { - return nil, ErrMissingDomain - } - - uri := fmt.Sprintf("/domains/%s/dns-records", resource.Domain) - response, err := api.makeRequest(ctx, http.MethodPost, uri, record) - if err != nil { - return nil, err - } - - res = &CreateDNSRecord_Response{} - err = json.Unmarshal(response, &res) - if err != nil { - return nil, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - - return res, nil -} - -// GetDNSRecord will return a single DNS record -// ? Documentation: https://www.arvancloud.ir/api/cdn/4.0#tag/DNS-Management/operation/dns_records.show -func (api *API) GetDNSRecord(ctx context.Context, resource Resource, recordID string) (*DNSRecord, error) { - if resource.Domain == "" { - return nil, ErrMissingDomain - } - - if recordID == "" { - return nil, ErrMissingDNSRecordID - } - - uri := fmt.Sprintf("/domains/%s/dns-records/%s", resource.Domain, recordID) - response, err := api.makeRequest(ctx, http.MethodGet, uri, nil) - if err != nil { - return nil, err - } - - res := &DNSRecord_Response{} - err = json.Unmarshal(response, &res) - if err != nil { - return nil, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - - return &res.Data, nil -} - -// ListDNSRecords will list a part of DNS records -// ? Documentation: https://www.arvancloud.ir/api/cdn/4.0#tag/DNS-Management/operation/dns_records.list -func (api *API) ListDNSRecords(ctx context.Context, resource Resource, params ListDNSRecordsParams) ([]DNSRecord, error) { - if resource.Domain == "" { - return nil, ErrMissingDomain - } - - if params.Page < 1 { - params.Page = 1 - } - - uri := buildURI(fmt.Sprintf("/domains/%s/dns-records", resource.Domain), params) - res, err := api.makeRequest(ctx, http.MethodGet, uri, nil) - if err != nil { - return nil, err - } - - var listResponse ListDNSRecord_Response - err = json.Unmarshal(res, &listResponse) - if err != nil { - return nil, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - - return listResponse.Data, nil -} - -// UpdateDNSRecord will update a DNS record -// ? Documentation: https://www.arvancloud.ir/api/cdn/4.0#tag/DNS-Management/operation/dns_records.update -func (api *API) UpdateDNSRecord(ctx context.Context, resource Resource, recordID string, params UpdateDNSRecordParams) (*UpdateDNSRecord_Response, error) { - if resource.Domain == "" { - return nil, ErrMissingDomain - } - - if recordID == "" { - return nil, ErrMissingDNSRecordID - } - - uri := fmt.Sprintf("/domains/%s/dns-records/%s", resource.Domain, recordID) - response, err := api.makeRequest(ctx, http.MethodPut, uri, params) - if err != nil { - return nil, err - } - - res := &UpdateDNSRecord_Response{} - err = json.Unmarshal(response, &res) - if err != nil { - return nil, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - - return res, nil -} - -// DeleteDNSRecord will delete a DNS record -// ? Documentation: https://www.arvancloud.ir/api/cdn/4.0#tag/DNS-Management/operation/dns_records.remove -func (api *API) DeleteDNSRecord(ctx context.Context, resource Resource, recordID string) (*DeleteDNSRecord_Response, error) { - if resource.Domain == "" { - return nil, ErrMissingDomain - } - - if recordID == "" { - return nil, ErrMissingDNSRecordID - } - - uri := fmt.Sprintf("/domains/%s/dns-records/%s", resource.Domain, recordID) - response, err := api.makeRequest(ctx, http.MethodDelete, uri, nil) - if err != nil { - return nil, err - } - - res := &DeleteDNSRecord_Response{} - err = json.Unmarshal(response, &res) - if err != nil { - return nil, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - - return res, nil -} diff --git a/pkg/dns_entity.go b/pkg/dns_entity.go deleted file mode 100644 index 6b1ab8e..0000000 --- a/pkg/dns_entity.go +++ /dev/null @@ -1,162 +0,0 @@ -package arvancloud - -// DSNRecord is a DSN record structure for a domain -type DNSRecord struct { - ID string `json:"id,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Value interface{} `json:"value,omitempty"` - TTL int `json:"ttl,omitempty"` - Cloud bool `json:"cloud,omitempty"` - UpstreamHTTPS string `json:"upstream_https,omitempty"` - IPFilterMode interface{} `json:"ip_filter_mode,omitempty"` - IsProtected bool `json:"is_protected,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` -} - -// DNSRecord_Response is response structure contains -// DSN record's data -type DNSRecord_Response struct { - Data DNSRecord `json:"data,omitempty"` -} - -// ----------------------------------------------------------- DSN Records - -// DNSRecord_Value_A is a structure for A record -type DNSRecord_Value_A struct { - IP string `json:"ip,omitempty"` - Port int `json:"port,omitempty"` - Weight int `json:"weight,omitempty"` - Country string `json:"country,omitempty"` -} - -// DNSRecord_Value_AAAA is a structure for AAAA record -type DNSRecord_Value_AAAA = DNSRecord_Value_A - -// DNSRecord_Value_MX is a structure for MX record -type DNSRecord_Value_MX struct { - Host string `json:"host,omitempty"` - Priority int `json:"priority,omitempty"` -} - -// DNSRecord_Value_NS is a structure for NS record -type DNSRecord_Value_NS struct { - Host string `json:"host,omitempty"` -} - -// DNSRecord_Value_SRV is a structure for SRV record -type DNSRecord_Value_SRV struct { - Target string `json:"target,omitempty"` - Port int `json:"port,omitempty"` - Weight int `json:"weight,omitempty"` - Priority int `json:"priority,omitempty"` -} - -// DNSRecord_Value_TXT is a structure for TXT record -type DNSRecord_Value_TXT struct { - Text string `json:"text,omitempty"` -} - -// DNSRecord_Value_SPF is a structure for SPF record -type DNSRecord_Value_SPF = DNSRecord_Value_TXT - -// DNSRecord_Value_DKIM is a structure for DKIM record -type DNSRecord_Value_DKIM = DNSRecord_Value_TXT - -// DNSRecord_Value_ANAME is a structure for ANAME record -type DNSRecord_Value_ANAME struct { - Location string `json:"location,omitempty"` - HostHeader string `json:"host_header,omitempty"` - Port int `json:"port,omitempty"` -} - -// DNSRecord_Value_CNAME is a structure for CNAME record -type DNSRecord_Value_CNAME struct { - Host string `json:"host,omitempty"` - HostHeader string `json:"host_header,omitempty"` - Port int `json:"port,omitempty"` -} - -// DNSRecord_Value_PTR is a structure for PTR record -type DNSRecord_Value_PTR struct { - Domain string `json:"domain,omitempty"` -} - -// DNSRecord_Value_TLSA is a structure for TLSA record -type DNSRecord_Value_TLSA struct { - Usage string `json:"usage,omitempty"` - Selector string `json:"selector,omitempty"` - MatchingType string `json:"matching_type,omitempty"` - Certificate string `json:"certificate,omitempty"` -} - -// DNSRecord_Value_CAA is a structure for CAA record -type DNSRecord_Value_CAA struct { - Value string `json:"value,omitempty"` - Tag string `json:"tag,omitempty"` -} - -// ----------------------------------------------------------- DSN Operations - Create - -// CreateDNSRecordParams is a structure for all needed parameters -// to create DNS record -type CreateDNSRecordParams struct { - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Value interface{} `json:"value,omitempty"` - TTL int `json:"ttl,omitempty"` - Cloud bool `json:"cloud,omitempty"` - UpstreamHTTPS string `json:"upstream_https,omitempty"` - IPFilterMode interface{} `json:"ip_filter_mode,omitempty"` -} - -// DNSRecord_IPFilterMode is a structure for IP Filter Mode when -// creating a DNS record -type DNSRecord_IPFilterMode struct { - Count string `json:"count,omitempty"` - Order string `json:"order,omitempty"` - GeoFilter string `json:"geo_filter,omitempty"` -} - -// CreateDNSRecord_Response is a response structure when creating -// a DNS record of a domain -type CreateDNSRecord_Response struct { - Message string `json:"message,omitempty"` - Status bool `json:"status,omitempty"` - Errors map[string][]string `json:"errors,omitempty"` - Data interface{} `json:"data,omitempty"` -} - -// ----------------------------------------------------------- DSN Operations - List - -// ListDNSRecordsParams is a structure for all needed parameters -// to list all DNS records of a domain -type ListDNSRecordsParams struct { - Search string `url:"search,omitempty"` - Type string `url:"type,omitempty"` - Page int `url:"page,omitempty"` - PerPage int `url:"per_page,omitempty"` -} - -// ListDNSRecord_Response is a response structure when listing -// DNS records of a domain -type ListDNSRecord_Response struct { - Data []DNSRecord `json:"data"` - Meta interface{} `json:"meta,omitempty"` - Links interface{} `json:"links,omitempty"` -} - -// ----------------------------------------------------------- DSN Operations - Update - -type UpdateDNSRecordParams = CreateDNSRecordParams - -// UpdateDNSRecord_Response is a response structure when updating -// a DNS record of a domain -type UpdateDNSRecord_Response = CreateDNSRecord_Response - -// ----------------------------------------------------------- DSN Operations - Delete - -// DeleteDNSRecord_Response is a response structure when deleting -// a DNS record of a domain -type DeleteDNSRecord_Response = CreateDNSRecord_Response diff --git a/pkg/dns_test.go b/pkg/dns_test.go deleted file mode 100644 index f29c94a..0000000 --- a/pkg/dns_test.go +++ /dev/null @@ -1,523 +0,0 @@ -package arvancloud - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateDNSRecord(t *testing.T) { - setup() - defer teardown() - - input := CreateDNSRecordParams{ - Type: "a", - Name: "@", - Value: []interface{}{ - map[string]interface{}{ - "ip": "1.2.3.4", - "port": 1.0, - "weight": 10.0, - "country": "", - }, - map[string]interface{}{ - "ip": "5.6.7.8", - "port": 2.0, - "weight": 20.0, - "country": "", - }, - }, - TTL: 120, - UpstreamHTTPS: "https", - IPFilterMode: map[string]interface{}{ - "count": "multi", - "order": "weighted", - "geo_filter": "none", - }, - } - - handler := func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) - - var p CreateDNSRecordParams - err := json.NewDecoder(r.Body).Decode(&p) - require.NoError(t, err) - assert.Equal(t, input, p) - - w.Header().Set("content-type", "application/json") - fmt.Fprint(w, `{ - "data": { - "id": "714009ff-a43c-43c5-80e2-0b3ffc1344a4", - "type": "a", - "name": "@", - "value": [ - { - "ip": "1.2.3.4", - "port": 1, - "weight": 10, - "country": "" - }, - { - "ip": "5.6.7.8", - "port": 2, - "weight": 20, - "country": "" - } - ], - "ttl": 120, - "cloud": false, - "upstream_https": "https", - "ip_filter_mode": { - "count": "multi", - "order": "weighted", - "geo_filter": "none" - }, - "is_protected": false, - "created_at": "2023-03-31T08:57:51+00:00", - "updated_at": "2023-03-31T08:57:51+00:00" - }, - "message": "DNS record created successfully" - }`) - } - - mux.HandleFunc("/domains/"+testDomain+"/dns-records", handler) - - want := &CreateDNSRecord_Response{ - Data: map[string]interface{}{ - "id": "714009ff-a43c-43c5-80e2-0b3ffc1344a4", - "type": input.Type, - "name": input.Name, - "value": input.Value, - "ttl": float64(input.TTL), - "cloud": false, - "upstream_https": input.UpstreamHTTPS, - "ip_filter_mode": input.IPFilterMode, - "is_protected": false, - "created_at": "2023-03-31T08:57:51+00:00", - "updated_at": "2023-03-31T08:57:51+00:00", - }, - Message: "DNS record created successfully", - } - - _, err := client.CreateDNSRecord(context.Background(), ResourceDomain(""), CreateDNSRecordParams{}) - assert.ErrorIs(t, err, ErrMissingDomain) - - actual, err := client.CreateDNSRecord(context.Background(), ResourceDomain(testDomain), input) - require.NoError(t, err) - - assert.Equal(t, want, actual) -} - -func TestGetDNSRecord(t *testing.T) { - setup() - defer teardown() - - recordID := "714009ff-a43c-43c5-80e2-0b3ffc1344a4" - - handler := func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) - - w.Header().Set("content-type", "application/json") - fmt.Fprint(w, `{ - "data": { - "id": "714009ff-a43c-43c5-80e2-0b3ffc1344a4", - "type": "a", - "name": "@", - "value": [ - { - "ip": "1.2.3.4", - "port": 1, - "weight": 10, - "country": "" - }, - { - "ip": "5.6.7.8", - "port": 2, - "weight": 20, - "country": "" - } - ], - "ttl": 120, - "cloud": false, - "upstream_https": "https", - "ip_filter_mode": { - "count": "multi", - "order": "weighted", - "geo_filter": "none" - }, - "created_at": "2023-03-31T08:57:51+00:00", - "updated_at": "2023-03-31T08:57:51+00:00" - } - }`) - } - - mux.HandleFunc("/domains/"+testDomain+"/dns-records/"+recordID, handler) - - want := &DNSRecord{ - ID: recordID, - Type: "a", - Name: "@", - Value: []interface{}{ - map[string]interface{}{ - "ip": "1.2.3.4", - "port": 1.0, - "weight": 10.0, - "country": "", - }, - map[string]interface{}{ - "ip": "5.6.7.8", - "port": 2.0, - "weight": 20.0, - "country": "", - }, - }, - TTL: 120, - Cloud: false, - UpstreamHTTPS: "https", - IPFilterMode: map[string]interface{}{ - "count": "multi", - "order": "weighted", - "geo_filter": "none", - }, - CreatedAt: "2023-03-31T08:57:51+00:00", - UpdatedAt: "2023-03-31T08:57:51+00:00", - } - - _, err := client.GetDNSRecord(context.Background(), ResourceDomain(""), recordID) - assert.ErrorIs(t, err, ErrMissingDomain) - - _, err = client.GetDNSRecord(context.Background(), ResourceDomain(testDomain), "") - assert.ErrorIs(t, err, ErrMissingDNSRecordID) - - actual, err := client.GetDNSRecord(context.Background(), ResourceDomain(testDomain), recordID) - require.NoError(t, err) - - assert.Equal(t, want, actual) -} - -func TestListDNSRecords(t *testing.T) { - setup() - defer teardown() - - input := ListDNSRecordsParams{ - Search: "b", - Type: "A", - } - - handler := func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) - assert.Equal(t, input.Search, r.URL.Query().Get("search")) - assert.Equal(t, input.Type, r.URL.Query().Get("type")) - - w.Header().Set("content-type", "application/json") - fmt.Fprint(w, `{ - "data": [ - { - "id": "714009ff-a43c-43c5-80e2-0b3ffc1344a4", - "type": "a", - "name": "@", - "value": [ - { - "ip": "1.2.3.4", - "port": 1, - "weight": 10, - "country": "" - }, - { - "ip": "5.6.7.8", - "port": 2, - "weight": 20, - "country": "" - } - ], - "ttl": 180, - "cloud": false, - "upstream_https": "https", - "ip_filter_mode": { - "count": "multi", - "order": "weighted", - "geo_filter": "none" - }, - "created_at": "2023-03-31T08:57:51+00:00", - "updated_at": "2023-03-31T15:41:26+00:00" - }, - { - "id": "ee29f172-0b0d-4f2d-ba12-9587291936e3", - "type": "a", - "name": "b", - "value": [ - { - "ip": "3.1.8.1", - "port": 80, - "weight": 100, - "country": "" - }, - { - "ip": "4.8.1.2", - "port": 80, - "weight": 100, - "country": "" - } - ], - "ttl": 120, - "cloud": false, - "upstream_https": "https", - "ip_filter_mode": { - "count": "multi", - "order": "weighted", - "geo_filter": "none" - }, - "created_at": "2023-03-31T11:40:15+00:00", - "updated_at": "2023-03-31T11:40:15+00:00" - } - ], - "links": { - "first": "https://napi.arvancloud.ir/4.0/domains/wkmag.ir/dns-records?page=1", - "last": "https://napi.arvancloud.ir/4.0/domains/wkmag.ir/dns-records?page=1", - "prev": null, - "next": null - }, - "meta": { - "current_page": 1, - "from": 1, - "last_page": 1, - "links": [ - { - "url": null, - "label": "Previous", - "active": false - }, - { - "url": "https://napi.arvancloud.ir/4.0/domains/wkmag.ir/dns-records?page=1", - "label": "1", - "active": true - }, - { - "url": null, - "label": "Next", - "active": false - } - ], - "path": "https://napi.arvancloud.ir/4.0/domains/wkmag.ir/dns-records", - "per_page": 300, - "to": 2, - "total": 2 - } - }`) - } - - mux.HandleFunc("/domains/"+testDomain+"/dns-records", handler) - - want := []DNSRecord{{ - ID: "714009ff-a43c-43c5-80e2-0b3ffc1344a4", - Type: "a", - Name: "@", - Value: []interface{}{ - map[string]interface{}{ - "ip": "1.2.3.4", - "port": 1.0, - "weight": 10.0, - "country": "", - }, - map[string]interface{}{ - "ip": "5.6.7.8", - "port": 2.0, - "weight": 20.0, - "country": "", - }, - }, - TTL: 180, - Cloud: false, - UpstreamHTTPS: "https", - IPFilterMode: map[string]interface{}{ - "count": "multi", - "order": "weighted", - "geo_filter": "none", - }, - CreatedAt: "2023-03-31T08:57:51+00:00", - UpdatedAt: "2023-03-31T15:41:26+00:00", - }, - { - ID: "ee29f172-0b0d-4f2d-ba12-9587291936e3", - Type: "a", - Name: "b", - Value: []interface{}{ - map[string]interface{}{ - "ip": "3.1.8.1", - "port": 80.0, - "weight": 100.0, - "country": "", - }, - map[string]interface{}{ - "ip": "4.8.1.2", - "port": 80.0, - "weight": 100.0, - "country": "", - }, - }, - TTL: 120, - Cloud: false, - UpstreamHTTPS: "https", - IPFilterMode: map[string]interface{}{ - "count": "multi", - "order": "weighted", - "geo_filter": "none", - }, - CreatedAt: "2023-03-31T11:40:15+00:00", - UpdatedAt: "2023-03-31T11:40:15+00:00", - }, - } - - _, err := client.ListDNSRecords(context.Background(), ResourceDomain(""), ListDNSRecordsParams{}) - assert.ErrorIs(t, err, ErrMissingDomain) - - actual, err := client.ListDNSRecords(context.Background(), ResourceDomain(testDomain), input) - require.NoError(t, err) - - assert.Equal(t, want, actual) -} - -func TestUpdateDNSRecord(t *testing.T) { - setup() - defer teardown() - - recordID := "714009ff-a43c-43c5-80e2-0b3ffc1344a4" - - input := UpdateDNSRecordParams{ - Type: "a", - Name: "@", - Value: []interface{}{ - map[string]interface{}{ - "ip": "1.2.3.5", - "port": 2.0, - "weight": 20.0, - "country": "", - }, - map[string]interface{}{ - "ip": "5.6.7.9", - "port": 3.0, - "weight": 30.0, - "country": "", - }, - }, - TTL: 180, - UpstreamHTTPS: "http", - IPFilterMode: map[string]interface{}{ - "count": "multi", - "order": "weighted", - "geo_filter": "none", - }, - } - - handler := func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) - - var p UpdateDNSRecordParams - err := json.NewDecoder(r.Body).Decode(&p) - require.NoError(t, err) - assert.Equal(t, input, p) - - w.Header().Set("content-type", "application/json") - fmt.Fprint(w, `{ - "data": { - "id": "714009ff-a43c-43c5-80e2-0b3ffc1344a4", - "type": "a", - "name": "@", - "value": [ - { - "ip": "1.2.3.5", - "port": 2, - "weight": 20, - "country": "" - }, - { - "ip": "5.6.7.9", - "port": 3, - "weight": 30, - "country": "" - } - ], - "ttl": 180, - "cloud": false, - "upstream_https": "http", - "ip_filter_mode": { - "count": "multi", - "order": "weighted", - "geo_filter": "none" - }, - "is_protected": false, - "created_at": "2023-03-31T08:57:51+00:00", - "updated_at": "2023-03-31T15:41:26+00:00" - }, - "message": "DNS record updated" - }`) - } - - mux.HandleFunc("/domains/"+testDomain+"/dns-records/"+recordID, handler) - - want := &UpdateDNSRecord_Response{ - Data: map[string]interface{}{ - "id": recordID, - "type": input.Type, - "name": input.Name, - "value": input.Value, - "ttl": float64(input.TTL), - "cloud": false, - "upstream_https": input.UpstreamHTTPS, - "ip_filter_mode": input.IPFilterMode, - "is_protected": false, - "created_at": "2023-03-31T08:57:51+00:00", - "updated_at": "2023-03-31T15:41:26+00:00", - }, - Message: "DNS record updated", - } - - _, err := client.UpdateDNSRecord(context.Background(), ResourceDomain(""), recordID, UpdateDNSRecordParams{}) - assert.ErrorIs(t, err, ErrMissingDomain) - - _, err = client.UpdateDNSRecord(context.Background(), ResourceDomain(testDomain), "", UpdateDNSRecordParams{}) - assert.ErrorIs(t, err, ErrMissingDNSRecordID) - - actual, err := client.UpdateDNSRecord(context.Background(), ResourceDomain(testDomain), recordID, input) - require.NoError(t, err) - - assert.Equal(t, want, actual) -} - -func TestDeleteDNSRecord(t *testing.T) { - setup() - defer teardown() - - recordID := "714009ff-a43c-43c5-80e2-0b3ffc1344a4" - - handler := func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method) - - w.Header().Set("content-type", "application/json") - fmt.Fprint(w, `{ - "message": "DNS record deleted" - }`) - } - - mux.HandleFunc("/domains/"+testDomain+"/dns-records/"+recordID, handler) - - want := &DeleteDNSRecord_Response{ - Message: "DNS record deleted", - } - - _, err := client.DeleteDNSRecord(context.Background(), ResourceDomain(""), recordID) - assert.ErrorIs(t, err, ErrMissingDomain) - - _, err = client.DeleteDNSRecord(context.Background(), ResourceDomain(testDomain), "") - assert.ErrorIs(t, err, ErrMissingDNSRecordID) - - actual, err := client.DeleteDNSRecord(context.Background(), ResourceDomain(testDomain), recordID) - require.NoError(t, err) - - assert.Equal(t, want, actual) -} diff --git a/pkg/entity.go b/pkg/entity.go deleted file mode 100644 index 3799392..0000000 --- a/pkg/entity.go +++ /dev/null @@ -1,11 +0,0 @@ -package arvancloud - -type Resource struct { - Domain string -} - -func ResourceDomain(domain string) Resource { - return Resource{ - Domain: domain, - } -} diff --git a/pkg/errors.go b/pkg/errors.go deleted file mode 100644 index 7baae97..0000000 --- a/pkg/errors.go +++ /dev/null @@ -1,156 +0,0 @@ -package arvancloud - -import ( - "errors" - "strings" -) - -const ( - errEmptyAPIToken = "invalid credentials: API Token must not be empty" - - errMissingDomain = "required missing domain" - errMissingDNSRecordID = "required DNS record ID missing" - - errUnmarshalError = "error unmarshalling the JSON response" - errUnmarshalErrorBody = "error unmarshalling the JSON response error body" - - errInternalServiceError = "internal service error" -) - -const ( - ErrorTypeRequest ErrorType = "request" - ErrorTypeAuthentication ErrorType = "authentication" - ErrorTypeAuthorization ErrorType = "authorization" - ErrorTypeNotFound ErrorType = "not_found" - ErrorTypeRateLimit ErrorType = "rate_limit" - ErrorTypeService ErrorType = "service" -) - -var ( - ErrMissingDomain = errors.New(errMissingDomain) - ErrMissingDNSRecordID = errors.New(errMissingDNSRecordID) -) - -type ErrorType string - -// Error is the error returned by the ArvanCloud API. -type Error struct { - // The classification of error encountered. - Type ErrorType - - // StatusCode is the HTTP status code from the response. - Code int - - // Message is the error message. - Message string - - // Errors is a list of all the response error. - Errors map[string][]string -} - -// Error will return a human readable error message. -func (e Error) Error() string { - var errString string - errMessages := []string{} - m := "" - if e.Message != "" { - m += e.Message - } - - // Append the main error message to the slice of error messages - errMessages = append(errMessages, m) - - errors := []string{} - - // Loop through each error in the Errors slice - // Join them into a comma-separated string - // Append the string to the errors slice - for _, e := range e.Errors { - errors = append(errors, strings.Join(e, ", ")) - } - - // Join the error messages with commas and add it to the errString variable - errString += strings.Join(errMessages, ", ") - - // If there are errors in the errors slice - // Join them with a new line and add them to the errString variable - if len(errors) > 0 { - errString += "\n" + strings.Join(errors, " \n") - } - - return errString -} - -// RequestError is for 4xx errors. -type RequestError struct { - arvancloudError *Error -} - -// Error will return a human readable error message. -func (e RequestError) Error() string { - return e.arvancloudError.Error() -} - -// Errors will return a map of all the errors. -func (e RequestError) Errors() map[string][]string { - return e.arvancloudError.Errors -} - -// ErrorCode will return the HTTP status code. -func (e RequestError) ErrorCode() int { - return e.arvancloudError.Code -} - -// ErrorMessage will return the error message. -func (e RequestError) ErrorMessage() string { - return e.arvancloudError.Message -} - -// Type will return the error type. -func (e RequestError) Type() ErrorType { - return e.arvancloudError.Type -} - -// NewRequestError will return a new RequestError. -func NewRequestError(e *Error) RequestError { - return RequestError{ - arvancloudError: e, - } -} - -// ServiceError is for 5xx errors. -type ServiceError struct { - arvancloudError *Error -} - -// Error will return a human readable error message. -func (e ServiceError) Error() string { - return e.arvancloudError.Error() -} - -// Errors will return a map of all the errors. -func (e ServiceError) Errors() map[string][]string { - return e.arvancloudError.Errors -} - -// ErrorCode will return the HTTP status code. -func (e ServiceError) ErrorCode() int { - return e.arvancloudError.Code -} - -// ErrorMessage will return the error message. -func (e ServiceError) ErrorMessage() string { - return e.arvancloudError.Message -} - -// Type will return the error type. -func (e ServiceError) Type() ErrorType { - return e.arvancloudError.Type -} - -// NewServiceError will return a new ServiceError. -func NewServiceError(e *Error) ServiceError { - return ServiceError{ - arvancloudError: e, - } -} diff --git a/pkg/info.go b/pkg/info.go deleted file mode 100644 index 79a1011..0000000 --- a/pkg/info.go +++ /dev/null @@ -1,18 +0,0 @@ -package arvancloud - -const ( - // SCHEME is the scheme used to connect to the ArvanCloud API - SCHEME = "https" - - // HOST_NAME is the host name used to connect to the ArvanCloud API - HOST_NAME = "napi.arvancloud.ir" - - // BASE_PATH is the base path used to connect to the CDN API - BASE_PATH = "/cdn/4.0" - - // UA is the user agent of client - UA = "r1c-go" - - // VERSION is the version of client - VERSION = "v0.1.3" -) diff --git a/pkg/options.go b/pkg/options.go deleted file mode 100644 index 7c46504..0000000 --- a/pkg/options.go +++ /dev/null @@ -1,71 +0,0 @@ -package arvancloud - -import ( - "net/http" - - "time" - - "golang.org/x/time/rate" -) - -type Option func(*API) error - -// Headers will set custom HTTP headers for API calls -func Headers(headers http.Header) Option { - return func(api *API) error { - api.headers = headers - return nil - } -} - -// UsingRateLimit will apply a rate limit policy to API client -func UsingRateLimit(rps float64) Option { - return func(api *API) error { - api.rateLimiter = rate.NewLimiter(rate.Limit(rps), 1) - return nil - } -} - -// UsingRetryPolicy will apply a retry policy to API client -func UsingRetryPolicy(maxRetries int, minRetryDelaySecs int, maxRetryDelaySecs int) Option { - return func(api *API) error { - api.retryPolicy = RetryPolicy{ - MaxRetries: maxRetries, - MinRetryDelay: time.Duration(minRetryDelaySecs) * time.Second, - MaxRetryDelay: time.Duration(maxRetryDelaySecs) * time.Second, - } - - return nil - } -} - -// UsingLogger will apply a custom user agent to API client -func UserAgent(userAgent string) Option { - return func(api *API) error { - api.UserAgent = userAgent - - return nil - } -} - -// Debug will handle debugging mode for API client -func Debug(debug bool) Option { - return func(api *API) error { - api.Debug = debug - - return nil - } -} - -// parseOptions will parse the supplied options for API client -func (api *API) parseOptions(options ...Option) error { - for _, option := range options { - err := option(api) - - if err != nil { - return err - } - } - - return nil -} diff --git a/pkg/options_test.go b/pkg/options_test.go deleted file mode 100644 index 2f3d2df..0000000 --- a/pkg/options_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package arvancloud - -import ( - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "golang.org/x/time/rate" -) - -func TestParseOptions(t *testing.T) { - headers := make(http.Header) - headers.Add("H1", "V1") - headers.Add("H2", "V2") - headers.Add("H3", "V3") - - api, err := New( - "TEST_TOKEN", - Debug(true), - UserAgent("UA-test"), - Headers(headers), - UsingRateLimit(1.5), - UsingRetryPolicy(1, 2, 3), - ) - - if err != nil { - t.Errorf("Error ocurred: %v", err.Error()) - } - - assert.Equal(t, true, api.Debug) - - assert.Equal(t, "UA-test", api.UserAgent) - - assert.Equal(t, "V1", api.headers.Get("H1")) - assert.Equal(t, "V2", api.headers.Get("H2")) - assert.Equal(t, "V3", api.headers.Get("H3")) - - assert.Equal(t, 1, api.rateLimiter.Burst()) - assert.Equal(t, rate.Limit(1.5), api.rateLimiter.Limit()) - - assert.Equal(t, 1, api.retryPolicy.MaxRetries) - assert.Equal(t, time.Duration(2000000000), api.retryPolicy.MinRetryDelay) - assert.Equal(t, time.Duration(3000000000), api.retryPolicy.MaxRetryDelay) -} diff --git a/pkg/utils.go b/pkg/utils.go deleted file mode 100644 index 835e5c0..0000000 --- a/pkg/utils.go +++ /dev/null @@ -1,26 +0,0 @@ -package arvancloud - -import ( - "encoding/json" - "fmt" - "net/url" - - "github.com/google/go-querystring/query" -) - -func buildURI(path string, options interface{}) string { - v, _ := query.Values(options) - return (&url.URL{Path: path, RawQuery: v.Encode()}).String() -} - -// PrettyPrint will print the contents of the object -func PrettyPrint(data interface{}) { - p, err := json.MarshalIndent(data, "", "\t") - - if err != nil { - fmt.Println(err) - return - } - - fmt.Printf("%s \n", p) -} diff --git a/pkg/utils_test.go b/pkg/utils_test.go deleted file mode 100644 index f7ecb2f..0000000 --- a/pkg/utils_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package arvancloud - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type Pagination struct { - Page int `json:"page,omitempty" url:"page,omitempty"` - PerPage int `json:"per_page,omitempty" url:"per_page,omitempty"` -} - -type testExample struct { - A string `url:"a,omitempty"` - B string `url:"b,omitempty"` - Pagination -} - -func Test_buildURI(t *testing.T) { - tests := map[string]struct { - path string - params interface{} - want string - }{ - "multi level": {path: "/accounts/test.ir", params: testExample{}, want: "/accounts/test.ir"}, - "multi level - params": {path: "/domains/test.ir", params: testExample{A: "b"}, want: "/domains/test.ir?a=b"}, - "multi level - multi params": {path: "/domains/test.ir", params: testExample{A: "b", B: "d"}, want: "/domains/test.ir?a=b&b=d"}, - "multi level - nested fields": {path: "/domains/test.ir", params: testExample{A: "b", B: "d", Pagination: Pagination{PerPage: 10}}, want: "/domains/test.ir?a=b&b=d&per_page=10"}, - "single level": {path: "/test", params: testExample{}, want: "/test"}, - "single level - params": {path: "/test", params: testExample{B: "d"}, want: "/test?b=d"}, - "single level - multi params": {path: "/test", params: testExample{A: "b", B: "d"}, want: "/test?a=b&b=d"}, - "single level - nested fields": {path: "/test", params: testExample{A: "b", B: "d", Pagination: Pagination{PerPage: 10}}, want: "/test?a=b&b=d&per_page=10"}, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - got := buildURI(tc.path, tc.params) - assert.Equal(t, tc.want, got) - }) - } -}