diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml deleted file mode 100644 index 5e35e48..0000000 --- a/.github/workflows/build-and-release.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: build-and-release - -on: - workflow_dispatch: - -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: version - id: version - run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT" - - run: git tag v${{ steps.version.outputs.version }} - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: 1.22.2 - - name: Run GoReleaser build - uses: goreleaser/goreleaser-action@v4 - with: - version: latest - args: release --verbose --clean - env: - GITHUB_TOKEN: ${{ secrets.GH_PAT }} - # - name: Upload darwin-amd64 - # uses: actions/upload-artifact@v3 - # with: - # name: anklet_v${{ steps.version.outputs.version }}_darwin_amd64 - # path: dist/anklet_v${{ steps.version.outputs.version }}_darwin_amd64.zip - # - name: Upload darwin-arm64 - # uses: actions/upload-artifact@v3 - # with: - # name: anklet_v${{ steps.version.outputs.version }}_darwin_arm64 - # path: dist/anklet_v${{ steps.version.outputs.version }}_darwin_arm64.zip - # - name: Upload linux-amd64 - # uses: actions/upload-artifact@v3 - # with: - # name: anklet_v${{ steps.version.outputs.version }}_linux_amd64 - # path: dist/anklet_v${{ steps.version.outputs.version }}_linux_amd64.zip - # - name: Upload linux-arm64 - # uses: actions/upload-artifact@v3 - # with: - # name: anklet_v${{ steps.version.outputs.version }}_linux_arm64 - # path: dist/anklet_v${{ steps.version.outputs.version }}_linux_arm64.zip diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index c619a30..c149e9b 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -3,8 +3,7 @@ name: Check PR on: workflow_dispatch: pull_request: - branches: [ master ] - + branches-ignore: [] jobs: goreleaser: diff --git a/.goreleaser.yml b/.goreleaser.yml index cd4dbdd..3a2e987 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -30,22 +30,10 @@ archives: files: - none* name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}' -checksum: - name_template: 'checksums.txt' -# signs: -# - artifacts: checksum -# args: -# # if you are using this is in a GitHub action or some other automated pipeline, you -# # need to pass the batch flag to indicate its not interactive. -# - "--batch" -# - "--local-user" -# - "{{ .Env.GPG_FINGERPRINT }}" -# - "--output" -# - "${signature}" -# - "--detach-sign" -# - "${artifact}" +# checksum: +# name_template: 'checksums.txt' release: - draft: true + disable: true # extra_files: # - glob: ./docs.zip # disable: true diff --git a/README.md b/README.md index 47738d6..d6d1054 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Anklet is fairly lightweight. When running 2 `github` plugin services, we see co ### How does it manage VM Templates on the host? Anklet handles VM [Templates/Tags](https://docs.veertu.com/anka/anka-virtualization-cli/getting-started/creating-vms/#vm-templates) the best it can using the Anka CLI. -- If the VM Template or Tag does not exist, Anklet will pull it from the Registry using the `default` configured registry under `anka registry list-repos`. +- If the VM Template or Tag does not exist, Anklet will pull it from the Registry using the `default` configured registry under `anka registry list-repos`. You can also set the `registry_url` in the `config.yml` to use a different registry. - Two consecutive pulls cannot happen on the same host or else the data may become corrupt. If a second job is picked up that requires a pull, it will send it back to the queue so another host can handle it. - If the Template *AND* Tag already exist, it does *not* issue a pull from the Registry (which therefore doesn't require maintaining a Registry at all; useful for users who use `anka export/import`). Important: You must define the tag, or else it will attempt to use "latest" and forcefully issue a pull. @@ -62,6 +62,8 @@ Anklet handles VM [Templates/Tags](https://docs.veertu.com/anka/anka-virtualizat registration: repo repo: anklet owner: veertuinc + registry_url: http://anka.registry:8089 + sleep_interval: 10 # sleep 10 seconds between checks for new jobs database: enabled: true url: localhost @@ -75,6 +77,7 @@ Anklet handles VM [Templates/Tags](https://docs.veertu.com/anka/anka-virtualizat registration: repo repo: anklet owner: veertuinc + registry_url: http://anka.registry:8089 database: enabled: true url: localhost @@ -84,6 +87,7 @@ Anklet handles VM [Templates/Tags](https://docs.veertu.com/anka/anka-virtualizat database: 0 ``` + > Note: You can only ever run two VMs per host per the Apple macOS SLA. While you can specify more than two services, only two will ever be running a VM at one time. `sleep_interval` can be used to control the frequency/priority of a service and increase the odds that a job will be picked up. 3. Run the daemon by executing `anklet` on the host that has the [Anka CLI installed](https://docs.veertu.com/anka/anka-virtualization-cli/getting-started/installing-the-anka-virtualization-package/). - `tail -fF /Users/myUser/Library/Logs/anklet.log` to see the logs. You can run `anklet` with `LOG_LEVEL=DEBUG` to see more verbose output. 3. To stop, you have two options: @@ -117,6 +121,7 @@ While you can run it anywhere you want, its likely going to be less latency to h brew install go go mod tidy LOG_LEVEL=dev go run main.go +tail -fF ~/Library/Logs/anklet.log ``` The `dev` LOG_LEVEL has colored output with text + pretty printed JSON for easier debugging. Here is an example: @@ -174,12 +179,4 @@ Each plugin must have a `{name}.go` file with a `Run` function that takes in `co The `Run` function should be designed to run multiple times in parallel. It should not rely on any state from the previous runs. - Always `return` out of `Run` so the sleep interval and main.go can handle the next run properly with new context. Never loop inside of the plugin code. - Should never panic but instead throw an ERROR and return. - - It's critical that you check for context cancellation before important logic that could orphan resources. - -TODO - -- Developer guide for writing plugins -- Timeouts for pulls, etc -- Check for user cancellation (from github) and cleanup -- Output inner actions-runner/_diag log on failure -- support org level registration for github plugin \ No newline at end of file + - It's critical that you check for context cancellation before important logic that could orphan resources. \ No newline at end of file diff --git a/internal/anka/cli.go b/internal/anka/cli.go index c531204..c81016c 100644 --- a/internal/anka/cli.go +++ b/internal/anka/cli.go @@ -145,13 +145,20 @@ func (cli *Cli) AnkaRegistryPull(ctx context.Context, template string, tag strin return fmt.Errorf("context canceled before AnkaRegistryPull") } logger := logging.GetLoggerFromContext(ctx) - logger.DebugContext(ctx, "pulling template to host") + service := config.GetServiceFromContext(ctx) + var registryExtra []string + if service.RegistryURL != "" { + registryExtra = []string{"--remote", service.RegistryURL} + } var args []string if tag != "(using latest)" { - args = []string{"anka", "-j", "registry", "pull", "--shrink", template, "--tag", tag} + args = append([]string{"anka", "-j", "registry"}, registryExtra...) + args = append(args, "pull", "--shrink", template, "--tag", tag) } else { - args = []string{"anka", "-j", "registry", "pull", "--shrink", template} + args = append([]string{"anka", "-j", "registry"}, registryExtra...) + args = append(args, "pull", "--shrink", template) } + logger.DebugContext(ctx, "pulling template to host") pulledTemplate, err := cli.ExecuteParseJson(ctx, args...) if err != nil { return err diff --git a/internal/config/config.go b/internal/config/config.go index 020e9f2..4b3a606 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -41,6 +41,7 @@ type Service struct { Repo string `yaml:"repo"` Owner string `yaml:"owner"` Database Database `yaml:"database"` + RegistryURL string `yaml:"registry_url"` } func LoadConfig(configPath string) (*Config, error) { diff --git a/plugins/github/README.md b/plugins/github/README.md index 9124329..5005b45 100644 --- a/plugins/github/README.md +++ b/plugins/github/README.md @@ -10,11 +10,12 @@ In the `config.yml`, you can define the `github` plugin as follows: services: - name: RUNNER1 plugin: github - token: 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 + token: github_pat_XXX # can be org or repo registration: repo repo: anklet owner: veertuinc + registry_url: http://anka.registry:8089 database: enabled: true url: localhost diff --git a/plugins/github/github.go b/plugins/github/github.go index 165516c..7b8e810 100644 --- a/plugins/github/github.go +++ b/plugins/github/github.go @@ -405,6 +405,7 @@ func Run(ctx context.Context, logger *slog.Logger) { } if *currentJob.Status == "completed" { jobCompleted = true + ctx = logging.AppendCtx(ctx, slog.String("conclusion", *currentJob.Conclusion)) logger.InfoContext(ctx, "job completed", "job_id", *workflowRunJob.Job.ID) } else if logCounter%2 == 0 { if ctx.Err() != nil { @@ -451,14 +452,7 @@ func removeSelfHostedRunner(ctx context.Context, vm anka.VM, workflowRunID int64 "ankaTemplateTag": "(using latest)", "err": "DELETE https://api.github.com/repos/veertuinc/anklet/actions/runners/142: 422 Bad request - Runner \"anklet-vm-\u003cuuid\u003e\" is still running a job\" []", */ - cancelResponse, _, cancelErr := ExecuteGitHubClientFunction[github.Response](ctx, logger, func() (*github.Response, *github.Response, error) { - resp, err := githubClient.Actions.CancelWorkflowRunByID(ctx, service.Owner, service.Repo, workflowRunID) - return resp, nil, err - }) - if cancelErr != nil || cancelResponse.Response.StatusCode != 202 { - logger.ErrorContext(ctx, "error executing githubClient.Actions.CancelWorkflowRunByID", "err", cancelErr, "response", cancelResponse) - return - } + cancelSent := false for { workflowRun, _, err := ExecuteGitHubClientFunction[github.WorkflowRun](ctx, logger, func() (*github.WorkflowRun, *github.Response, error) { workflowRun, resp, err := githubClient.Actions.GetWorkflowRunByID(context.Background(), service.Owner, service.Repo, workflowRunID) @@ -472,6 +466,17 @@ func removeSelfHostedRunner(ctx context.Context, vm anka.VM, workflowRunID int64 break } else { logger.WarnContext(ctx, "workflow run is still active... waiting for cancellation so we can clean up the runner...", "workflow_run_id", workflowRunID) + if !cancelSent { // this has to happen here so that it doesn't error with "409 Cannot cancel a workflow run that is completed. " if the job is already cancelled + cancelResponse, _, cancelErr := ExecuteGitHubClientFunction[github.Response](ctx, logger, func() (*github.Response, *github.Response, error) { + resp, err := githubClient.Actions.CancelWorkflowRunByID(ctx, service.Owner, service.Repo, workflowRunID) + return resp, nil, err + }) + if cancelErr != nil || cancelResponse.Response.StatusCode != 202 { + logger.ErrorContext(ctx, "error executing githubClient.Actions.CancelWorkflowRunByID", "err", cancelErr, "response", cancelResponse) + break + } + cancelSent = true + } time.Sleep(20 * time.Second) } }