diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000000..36d741ea43 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,27 @@ +name: Trigger External Workflow on Release + +on: + release: + types: [published] + +jobs: + trigger-external-workflow: + runs-on: ubuntu-latest + steps: + - name: Trigger release workflow + if: github.event.release.prerelease == false + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.GITHUBBOT_TOKEN }} + repository: daytonaio/daytona-release + event-type: new-release + client-payload: '{"release_tag": "${{ github.event.release.tag_name }}"}' + + - name: Trigger prerelease workflow + if: github.event.release.prerelease == true + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.GITHUBBOT_TOKEN }} + repository: daytonaio/daytona-release + event-type: new-prerelease + client-payload: '{"prerelease_tag": "${{ github.event.release.tag_name }}"}' diff --git a/README.md b/README.md index 264177acd4..df23d586f3 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Set up a development environment on any infrastructure, with a single command. * __Configuration File Support__: Initially support for [dev container](https://containers.dev/), ability to expand to DevFile, Nix & Flox (Contributions welcome here!). * __Prebuilds System__: Drastically improve environment setup times (Contributions welcome here!). * __IDE Support__ : Seamlessly supports [VS Code](https://github.com/microsoft/vscode) & [JetBrains](https://www.jetbrains.com/remote-development/gateway/) locally, ready to use without configuration. Includes a built-in Web IDE for added convenience. -* __Git Provider Integration__: GitHub, GitLab, Bitbucket, Bitbucket Server, Gitea, Gitness, Azure DevOps & AWS CodeCommit can be connected, allowing easy repo branch or PR pull and commit back from the workspaces. +* __Git Provider Integration__: GitHub, GitLab, Bitbucket, Bitbucket Server, Gitea, Gitness, Azure DevOps, AWS CodeCommit & Gogs can be connected, allowing easy repo branch or PR pull and commit back from the workspaces. * __Multiple Project Workspace__: Support for multiple project repositories in the same workspace, making it easy to develop using a micro-service architecture. * __Reverse Proxy Integration__: Enable collaboration and streamline feedback loops by leveraging reverse proxy functionality. Access preview ports and the Web IDE seamlessly, even behind firewalls. * __Extensibility__: Enable extensibility with plugin or provider development. Moreover, in any dynamic language, not just Go(Contributions welcome here!). @@ -165,7 +165,7 @@ This initiates the Daytona Server in daemon mode. Use the command: daytona server ``` __2. Add Your Git Provider of Choice:__ -Daytona supports GitHub, GitLab, Bitbucket, Bitbucket Server, Gitea, Gitness, AWS CodeCommit and Azure DevOps. To add them to your profile, use the command: +Daytona supports GitHub, GitLab, Bitbucket, Bitbucket Server, Gitea, Gitness, AWS CodeCommit, Azure DevOps and Gogs. To add them to your profile, use the command: ```bash daytona git-providers add diff --git a/cmd/daytona/config/const.go b/cmd/daytona/config/const.go index 4a75d6bbea..6606f9f7fd 100644 --- a/cmd/daytona/config/const.go +++ b/cmd/daytona/config/const.go @@ -57,6 +57,7 @@ func GetSupportedGitProviders() []GitProvider { {"gitness", "Gitness"}, {"azure-devops", "Azure DevOps"}, {"aws-codecommit", "AWS CodeCommit"}, + {"gogs", "Gogs"}, } } @@ -84,6 +85,8 @@ func GetDocsLinkFromGitProvider(providerId string) string { return "https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#create-a-pat" case "aws-codecommit": return "https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-gc.html and to configure AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY & AWS_DEFAULT_REGION read https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html" + case "gogs": + return "https://www.daytona.io/docs/configuration/git-providers/#gogs" default: return "" } @@ -130,6 +133,8 @@ func GetRequiredScopesFromGitProviderId(providerId string) string { return "Code (Status, Read & Write); User Profile (Read); Project and Team (Read)" case "aws-codecommit": return "/" + case "gogs": + fallthrough default: return "" } @@ -164,6 +169,8 @@ func GetWebhookEventHeaderKeyFromGitProvider(providerId string) string { return "X-Gitea-Event" case "azure-devops": return "X-AzureDevops-Event" + case "gitness": + return "X-Gitness-Trigger" default: return "" } diff --git a/docs/daytona_git-providers_delete.md b/docs/daytona_git-providers_delete.md index d1a58999db..ab7a22aa23 100644 --- a/docs/daytona_git-providers_delete.md +++ b/docs/daytona_git-providers_delete.md @@ -10,6 +10,7 @@ daytona git-providers delete [flags] ``` -a, --all Remove all Git providers + -y, --yes Confirm deletion without prompt ``` ### Options inherited from parent commands diff --git a/docs/daytona_target.md b/docs/daytona_target.md index ef81ee4dbf..32b4b9f7f3 100644 --- a/docs/daytona_target.md +++ b/docs/daytona_target.md @@ -14,4 +14,5 @@ Manage provider targets * [daytona target list](daytona_target_list.md) - List targets * [daytona target remove](daytona_target_remove.md) - Remove target * [daytona target set](daytona_target_set.md) - Set provider target +* [daytona target set-default](daytona_target_set-default.md) - Set target to be used by default diff --git a/docs/daytona_target_set-default.md b/docs/daytona_target_set-default.md new file mode 100644 index 0000000000..d37fb3155c --- /dev/null +++ b/docs/daytona_target_set-default.md @@ -0,0 +1,18 @@ +## daytona target set-default + +Set target to be used by default + +``` +daytona target set-default [TARGET_NAME] [flags] +``` + +### Options inherited from parent commands + +``` + --help help for daytona +``` + +### SEE ALSO + +* [daytona target](daytona_target.md) - Manage provider targets + diff --git a/go.mod b/go.mod index 943300445b..88af4d3a56 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/go-git/go-git/v5 v5.12.1-0.20240617075238-c127d1b35535 github.com/go-playground/validator/v10 v10.19.0 github.com/go-playground/webhooks/v6 v6.4.0 + github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.1 github.com/hashicorp/go-hclog v1.6.3 diff --git a/go.sum b/go.sum index 1d9933ad6d..c1c33b3c41 100644 --- a/go.sum +++ b/go.sum @@ -953,6 +953,8 @@ github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0= +github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= diff --git a/hack/docs/daytona_git-providers_delete.yaml b/hack/docs/daytona_git-providers_delete.yaml index 849bc2bb9d..95d0109933 100644 --- a/hack/docs/daytona_git-providers_delete.yaml +++ b/hack/docs/daytona_git-providers_delete.yaml @@ -6,6 +6,10 @@ options: shorthand: a default_value: "false" usage: Remove all Git providers + - name: "yes" + shorthand: "y" + default_value: "false" + usage: Confirm deletion without prompt inherited_options: - name: help default_value: "false" diff --git a/hack/docs/daytona_target.yaml b/hack/docs/daytona_target.yaml index 3549650232..92ce570824 100644 --- a/hack/docs/daytona_target.yaml +++ b/hack/docs/daytona_target.yaml @@ -9,3 +9,4 @@ see_also: - daytona target list - List targets - daytona target remove - Remove target - daytona target set - Set provider target + - daytona target set-default - Set target to be used by default diff --git a/hack/docs/daytona_target_set-default.yaml b/hack/docs/daytona_target_set-default.yaml new file mode 100644 index 0000000000..138758fbd2 --- /dev/null +++ b/hack/docs/daytona_target_set-default.yaml @@ -0,0 +1,9 @@ +name: daytona target set-default +synopsis: Set target to be used by default +usage: daytona target set-default [TARGET_NAME] [flags] +inherited_options: + - name: help + default_value: "false" + usage: help for daytona +see_also: + - daytona target - Manage provider targets diff --git a/internal/testing/provider/targets/store.go b/internal/testing/provider/targets/store.go index 1539e0693b..160089e340 100644 --- a/internal/testing/provider/targets/store.go +++ b/internal/testing/provider/targets/store.go @@ -6,6 +6,8 @@ package targets import ( + "fmt" + "github.com/daytonaio/daytona/pkg/provider" ) @@ -19,22 +21,20 @@ func NewInMemoryTargetStore() provider.TargetStore { } } -func (s *InMemoryTargetStore) List() ([]*provider.ProviderTarget, error) { - targets := []*provider.ProviderTarget{} - for _, t := range s.targets { - targets = append(targets, t) - } - - return targets, nil +func (s *InMemoryTargetStore) List(filter *provider.TargetFilter) ([]*provider.ProviderTarget, error) { + return s.processFilters(filter) } -func (s *InMemoryTargetStore) Find(targetName string) (*provider.ProviderTarget, error) { - target, ok := s.targets[targetName] - if !ok { +func (s *InMemoryTargetStore) Find(filter *provider.TargetFilter) (*provider.ProviderTarget, error) { + targets, err := s.processFilters(filter) + if err != nil { + return nil, err + } + if len(targets) == 0 { return nil, provider.ErrTargetNotFound } - return target, nil + return targets[0], nil } func (s *InMemoryTargetStore) Save(target *provider.ProviderTarget) error { @@ -46,3 +46,35 @@ func (s *InMemoryTargetStore) Delete(target *provider.ProviderTarget) error { delete(s.targets, target.Name) return nil } + +func (s *InMemoryTargetStore) processFilters(filter *provider.TargetFilter) ([]*provider.ProviderTarget, error) { + var result []*provider.ProviderTarget + filteredTargets := make(map[string]*provider.ProviderTarget) + for k, v := range s.targets { + filteredTargets[k] = v + } + + if filter != nil { + if filter.Name != nil { + target, ok := s.targets[*filter.Name] + if ok { + return []*provider.ProviderTarget{target}, nil + } else { + return []*provider.ProviderTarget{}, fmt.Errorf("target with name %s not found", *filter.Name) + } + } + if filter.Default != nil { + for _, target := range filteredTargets { + if target.IsDefault != *filter.Default { + delete(filteredTargets, target.Name) + } + } + } + } + + for _, target := range filteredTargets { + result = append(result, target) + } + + return result, nil +} diff --git a/internal/util/apiclient/conversion/target.go b/internal/util/apiclient/conversion/target.go new file mode 100644 index 0000000000..2ad2ba2f33 --- /dev/null +++ b/internal/util/apiclient/conversion/target.go @@ -0,0 +1,17 @@ +// Copyright 2024 Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package conversion + +import ( + "github.com/daytonaio/daytona/pkg/provider" + "github.com/daytonaio/daytona/pkg/server/providertargets/dto" +) + +func ToProviderTarget(createProviderTargetDto dto.CreateProviderTargetDTO) *provider.ProviderTarget { + return &provider.ProviderTarget{ + Name: createProviderTargetDto.Name, + ProviderInfo: createProviderTargetDto.ProviderInfo, + Options: createProviderTargetDto.Options, + } +} diff --git a/pkg/api/controllers/target/list.go b/pkg/api/controllers/target/list.go index 40971e78f4..d60a913125 100644 --- a/pkg/api/controllers/target/list.go +++ b/pkg/api/controllers/target/list.go @@ -24,7 +24,7 @@ import ( func ListTargets(ctx *gin.Context) { server := server.GetInstance(nil) - targets, err := server.ProviderTargetService.List() + targets, err := server.ProviderTargetService.List(nil) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to list targets: %w", err)) return diff --git a/pkg/api/controllers/target/remove.go b/pkg/api/controllers/target/remove.go index 558852ff10..dcf61ea6ce 100644 --- a/pkg/api/controllers/target/remove.go +++ b/pkg/api/controllers/target/remove.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" + "github.com/daytonaio/daytona/pkg/provider" "github.com/daytonaio/daytona/pkg/server" "github.com/gin-gonic/gin" ) @@ -26,7 +27,9 @@ func RemoveTarget(ctx *gin.Context) { server := server.GetInstance(nil) - target, err := server.ProviderTargetService.Find(targetName) + target, err := server.ProviderTargetService.Find(&provider.TargetFilter{ + Name: &targetName, + }) if err != nil { ctx.AbortWithError(http.StatusNotFound, fmt.Errorf("failed to find target: %w", err)) return diff --git a/pkg/api/controllers/target/set.go b/pkg/api/controllers/target/set.go index 9d698ab4b7..93b681f23e 100644 --- a/pkg/api/controllers/target/set.go +++ b/pkg/api/controllers/target/set.go @@ -7,8 +7,10 @@ import ( "fmt" "net/http" + "github.com/daytonaio/daytona/internal/util/apiclient/conversion" "github.com/daytonaio/daytona/pkg/provider" "github.com/daytonaio/daytona/pkg/server" + "github.com/daytonaio/daytona/pkg/server/providertargets/dto" "github.com/gin-gonic/gin" ) @@ -17,13 +19,13 @@ import ( // @Tags target // @Summary Set a target // @Description Set a target -// @Param target body ProviderTarget true "Target to set" +// @Param target body CreateProviderTargetDTO true "Target to set" // @Success 201 // @Router /target [put] // // @id SetTarget func SetTarget(ctx *gin.Context) { - var req provider.ProviderTarget + var req dto.CreateProviderTargetDTO err := ctx.BindJSON(&req) if err != nil { ctx.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) @@ -32,13 +34,7 @@ func SetTarget(ctx *gin.Context) { server := server.GetInstance(nil) - target, err := server.ProviderTargetService.Find(req.Name) - if err == nil { - target.Options = req.Options - target.ProviderInfo = req.ProviderInfo - } else { - target = &req - } + target := conversion.ToProviderTarget(req) err = server.ProviderTargetService.Save(target) if err != nil { @@ -48,3 +44,35 @@ func SetTarget(ctx *gin.Context) { ctx.Status(201) } + +// SetDefaultTarget godoc +// +// @Tags target +// @Summary Set target to default +// @Description Set target to default +// @Param target path string true "Target name" +// @Success 200 +// @Router /target/{target}/set-default [patch] +// +// @id SetDefaultTarget +func SetDefaultTarget(ctx *gin.Context) { + targetName := ctx.Param("target") + + server := server.GetInstance(nil) + + target, err := server.ProviderTargetService.Find(&provider.TargetFilter{ + Name: &targetName, + }) + if err != nil { + ctx.AbortWithError(http.StatusNotFound, fmt.Errorf("failed to find target: %w", err)) + return + } + + err = server.ProviderTargetService.SetDefault(target) + if err != nil { + ctx.AbortWithError(http.StatusNotFound, fmt.Errorf("failed to set project config to default: %s", err.Error())) + return + } + + ctx.Status(200) +} diff --git a/pkg/api/docs/docs.go b/pkg/api/docs/docs.go index 1a6420214a..4da6f466a7 100644 --- a/pkg/api/docs/docs.go +++ b/pkg/api/docs/docs.go @@ -1522,7 +1522,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/ProviderTarget" + "$ref": "#/definitions/CreateProviderTargetDTO" } } ], @@ -1557,6 +1557,30 @@ const docTemplate = `{ } } }, + "/target/{target}/set-default": { + "patch": { + "description": "Set target to default", + "tags": [ + "target" + ], + "summary": "Set target to default", + "operationId": "SetDefaultTarget", + "parameters": [ + { + "type": "string", + "description": "Target name", + "name": "target", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/workspace": { "get": { "description": "List workspaces", @@ -2107,6 +2131,25 @@ const docTemplate = `{ } } }, + "CreateProviderTargetDTO": { + "type": "object", + "required": [ + "name", + "options", + "providerInfo" + ], + "properties": { + "name": { + "type": "string" + }, + "options": { + "type": "string" + }, + "providerInfo": { + "$ref": "#/definitions/provider.ProviderInfo" + } + } + }, "CreateWorkspaceDTO": { "type": "object", "required": [ @@ -2680,11 +2723,15 @@ const docTemplate = `{ "ProviderTarget": { "type": "object", "required": [ + "isDefault", "name", "options", "providerInfo" ], "properties": { + "isDefault": { + "type": "boolean" + }, "name": { "type": "string" }, diff --git a/pkg/api/docs/swagger.json b/pkg/api/docs/swagger.json index 16b10e422d..96e17c2048 100644 --- a/pkg/api/docs/swagger.json +++ b/pkg/api/docs/swagger.json @@ -1519,7 +1519,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/ProviderTarget" + "$ref": "#/definitions/CreateProviderTargetDTO" } } ], @@ -1554,6 +1554,30 @@ } } }, + "/target/{target}/set-default": { + "patch": { + "description": "Set target to default", + "tags": [ + "target" + ], + "summary": "Set target to default", + "operationId": "SetDefaultTarget", + "parameters": [ + { + "type": "string", + "description": "Target name", + "name": "target", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/workspace": { "get": { "description": "List workspaces", @@ -2104,6 +2128,25 @@ } } }, + "CreateProviderTargetDTO": { + "type": "object", + "required": [ + "name", + "options", + "providerInfo" + ], + "properties": { + "name": { + "type": "string" + }, + "options": { + "type": "string" + }, + "providerInfo": { + "$ref": "#/definitions/provider.ProviderInfo" + } + } + }, "CreateWorkspaceDTO": { "type": "object", "required": [ @@ -2677,11 +2720,15 @@ "ProviderTarget": { "type": "object", "required": [ + "isDefault", "name", "options", "providerInfo" ], "properties": { + "isDefault": { + "type": "boolean" + }, "name": { "type": "string" }, diff --git a/pkg/api/docs/swagger.yaml b/pkg/api/docs/swagger.yaml index 9c15a45ad6..2a8ad5ccb5 100644 --- a/pkg/api/docs/swagger.yaml +++ b/pkg/api/docs/swagger.yaml @@ -185,6 +185,19 @@ definitions: required: - repository type: object + CreateProviderTargetDTO: + properties: + name: + type: string + options: + type: string + providerInfo: + $ref: '#/definitions/provider.ProviderInfo' + required: + - name + - options + - providerInfo + type: object CreateWorkspaceDTO: properties: id: @@ -578,6 +591,8 @@ definitions: type: object ProviderTarget: properties: + isDefault: + type: boolean name: type: string options: @@ -586,6 +601,7 @@ definitions: providerInfo: $ref: '#/definitions/provider.ProviderInfo' required: + - isDefault - name - options - providerInfo @@ -1883,7 +1899,7 @@ paths: name: target required: true schema: - $ref: '#/definitions/ProviderTarget' + $ref: '#/definitions/CreateProviderTargetDTO' responses: "201": description: Created @@ -1906,6 +1922,22 @@ paths: summary: Remove a target tags: - target + /target/{target}/set-default: + patch: + description: Set target to default + operationId: SetDefaultTarget + parameters: + - description: Target name + in: path + name: target + required: true + type: string + responses: + "200": + description: OK + summary: Set target to default + tags: + - target /workspace: get: description: List workspaces diff --git a/pkg/api/server.go b/pkg/api/server.go index cb66be76d2..e58fa606aa 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -208,6 +208,7 @@ func (a *ApiServer) Start() error { { targetController.GET("/", target.ListTargets) targetController.PUT("/", target.SetTarget) + targetController.PATCH("/:target/set-default", target.SetDefaultTarget) targetController.DELETE("/:target", target.RemoveTarget) } diff --git a/pkg/apiclient/README.md b/pkg/apiclient/README.md index 5c2e1633f7..5ba3d8f89c 100644 --- a/pkg/apiclient/README.md +++ b/pkg/apiclient/README.md @@ -129,6 +129,7 @@ Class | Method | HTTP request | Description *ServerAPI* | [**SetConfig**](docs/ServerAPI.md#setconfig) | **Post** /server/config | Set the server configuration *TargetAPI* | [**ListTargets**](docs/TargetAPI.md#listtargets) | **Get** /target | List targets *TargetAPI* | [**RemoveTarget**](docs/TargetAPI.md#removetarget) | **Delete** /target/{target} | Remove a target +*TargetAPI* | [**SetDefaultTarget**](docs/TargetAPI.md#setdefaulttarget) | **Patch** /target/{target}/set-default | Set target to default *TargetAPI* | [**SetTarget**](docs/TargetAPI.md#settarget) | **Put** /target | Set a target *WorkspaceAPI* | [**CreateWorkspace**](docs/WorkspaceAPI.md#createworkspace) | **Post** /workspace | Create a workspace *WorkspaceAPI* | [**GetWorkspace**](docs/WorkspaceAPI.md#getworkspace) | **Get** /workspace/{workspaceId} | Get workspace info @@ -157,6 +158,7 @@ Class | Method | HTTP request | Description - [CreateProjectConfigDTO](docs/CreateProjectConfigDTO.md) - [CreateProjectDTO](docs/CreateProjectDTO.md) - [CreateProjectSourceDTO](docs/CreateProjectSourceDTO.md) + - [CreateProviderTargetDTO](docs/CreateProviderTargetDTO.md) - [CreateWorkspaceDTO](docs/CreateWorkspaceDTO.md) - [DevcontainerConfig](docs/DevcontainerConfig.md) - [FRPSConfig](docs/FRPSConfig.md) diff --git a/pkg/apiclient/api/openapi.yaml b/pkg/apiclient/api/openapi.yaml index 8db9bbd616..e04f42f998 100644 --- a/pkg/apiclient/api/openapi.yaml +++ b/pkg/apiclient/api/openapi.yaml @@ -1079,7 +1079,7 @@ paths: content: '*/*': schema: - $ref: '#/components/schemas/ProviderTarget' + $ref: '#/components/schemas/CreateProviderTargetDTO' description: Target to set required: true responses: @@ -1108,6 +1108,24 @@ paths: summary: Remove a target tags: - target + /target/{target}/set-default: + patch: + description: Set target to default + operationId: SetDefaultTarget + parameters: + - description: Target name + in: path + name: target + required: true + schema: + type: string + responses: + "200": + content: {} + description: OK + summary: Set target to default + tags: + - target /workspace: get: description: List workspaces @@ -1617,6 +1635,26 @@ components: required: - repository type: object + CreateProviderTargetDTO: + example: + name: name + options: options + providerInfo: + name: name + label: label + version: version + properties: + name: + type: string + options: + type: string + providerInfo: + $ref: '#/components/schemas/provider.ProviderInfo' + required: + - name + - options + - providerInfo + type: object CreateWorkspaceDTO: example: projects: @@ -2263,6 +2301,7 @@ components: type: object ProviderTarget: example: + isDefault: true name: name options: options providerInfo: @@ -2270,6 +2309,8 @@ components: label: label version: version properties: + isDefault: + type: boolean name: type: string options: @@ -2278,6 +2319,7 @@ components: providerInfo: $ref: '#/components/schemas/provider.ProviderInfo' required: + - isDefault - name - options - providerInfo diff --git a/pkg/apiclient/api_target.go b/pkg/apiclient/api_target.go index 143c5ce7da..09ace864e4 100644 --- a/pkg/apiclient/api_target.go +++ b/pkg/apiclient/api_target.go @@ -242,14 +242,120 @@ func (a *TargetAPIService) RemoveTargetExecute(r ApiRemoveTargetRequest) (*http. return localVarHTTPResponse, nil } +type ApiSetDefaultTargetRequest struct { + ctx context.Context + ApiService *TargetAPIService + target string +} + +func (r ApiSetDefaultTargetRequest) Execute() (*http.Response, error) { + return r.ApiService.SetDefaultTargetExecute(r) +} + +/* +SetDefaultTarget Set target to default + +Set target to default + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @param target Target name + @return ApiSetDefaultTargetRequest +*/ +func (a *TargetAPIService) SetDefaultTarget(ctx context.Context, target string) ApiSetDefaultTargetRequest { + return ApiSetDefaultTargetRequest{ + ApiService: a, + ctx: ctx, + target: target, + } +} + +// Execute executes the request +func (a *TargetAPIService) SetDefaultTargetExecute(r ApiSetDefaultTargetRequest) (*http.Response, error) { + var ( + localVarHTTPMethod = http.MethodPatch + localVarPostBody interface{} + formFiles []formFile + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "TargetAPIService.SetDefaultTarget") + if err != nil { + return nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/target/{target}/set-default" + localVarPath = strings.Replace(localVarPath, "{"+"target"+"}", url.PathEscape(parameterValueToString(r.target, "target")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.ctx != nil { + // API Key Authentication + if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok { + if apiKey, ok := auth["Bearer"]; ok { + var key string + if apiKey.Prefix != "" { + key = apiKey.Prefix + " " + apiKey.Key + } else { + key = apiKey.Key + } + localVarHeaderParams["Authorization"] = key + } + } + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + return localVarHTTPResponse, newErr + } + + return localVarHTTPResponse, nil +} + type ApiSetTargetRequest struct { ctx context.Context ApiService *TargetAPIService - target *ProviderTarget + target *CreateProviderTargetDTO } // Target to set -func (r ApiSetTargetRequest) Target(target ProviderTarget) ApiSetTargetRequest { +func (r ApiSetTargetRequest) Target(target CreateProviderTargetDTO) ApiSetTargetRequest { r.target = &target return r } diff --git a/pkg/apiclient/docs/CreateProviderTargetDTO.md b/pkg/apiclient/docs/CreateProviderTargetDTO.md new file mode 100644 index 0000000000..87e2ba03a3 --- /dev/null +++ b/pkg/apiclient/docs/CreateProviderTargetDTO.md @@ -0,0 +1,93 @@ +# CreateProviderTargetDTO + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Name** | **string** | | +**Options** | **string** | | +**ProviderInfo** | [**ProviderProviderInfo**](ProviderProviderInfo.md) | | + +## Methods + +### NewCreateProviderTargetDTO + +`func NewCreateProviderTargetDTO(name string, options string, providerInfo ProviderProviderInfo, ) *CreateProviderTargetDTO` + +NewCreateProviderTargetDTO instantiates a new CreateProviderTargetDTO object +This constructor will assign default values to properties that have it defined, +and makes sure properties required by API are set, but the set of arguments +will change when the set of required properties is changed + +### NewCreateProviderTargetDTOWithDefaults + +`func NewCreateProviderTargetDTOWithDefaults() *CreateProviderTargetDTO` + +NewCreateProviderTargetDTOWithDefaults instantiates a new CreateProviderTargetDTO object +This constructor will only assign default values to properties that have it defined, +but it doesn't guarantee that properties required by API are set + +### GetName + +`func (o *CreateProviderTargetDTO) GetName() string` + +GetName returns the Name field if non-nil, zero value otherwise. + +### GetNameOk + +`func (o *CreateProviderTargetDTO) GetNameOk() (*string, bool)` + +GetNameOk returns a tuple with the Name field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetName + +`func (o *CreateProviderTargetDTO) SetName(v string)` + +SetName sets Name field to given value. + + +### GetOptions + +`func (o *CreateProviderTargetDTO) GetOptions() string` + +GetOptions returns the Options field if non-nil, zero value otherwise. + +### GetOptionsOk + +`func (o *CreateProviderTargetDTO) GetOptionsOk() (*string, bool)` + +GetOptionsOk returns a tuple with the Options field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetOptions + +`func (o *CreateProviderTargetDTO) SetOptions(v string)` + +SetOptions sets Options field to given value. + + +### GetProviderInfo + +`func (o *CreateProviderTargetDTO) GetProviderInfo() ProviderProviderInfo` + +GetProviderInfo returns the ProviderInfo field if non-nil, zero value otherwise. + +### GetProviderInfoOk + +`func (o *CreateProviderTargetDTO) GetProviderInfoOk() (*ProviderProviderInfo, bool)` + +GetProviderInfoOk returns a tuple with the ProviderInfo field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetProviderInfo + +`func (o *CreateProviderTargetDTO) SetProviderInfo(v ProviderProviderInfo)` + +SetProviderInfo sets ProviderInfo field to given value. + + + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/pkg/apiclient/docs/ProviderTarget.md b/pkg/apiclient/docs/ProviderTarget.md index 08ab4e874c..e4592a685d 100644 --- a/pkg/apiclient/docs/ProviderTarget.md +++ b/pkg/apiclient/docs/ProviderTarget.md @@ -4,6 +4,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**IsDefault** | **bool** | | **Name** | **string** | | **Options** | **string** | JSON encoded map of options | **ProviderInfo** | [**ProviderProviderInfo**](ProviderProviderInfo.md) | | @@ -12,7 +13,7 @@ Name | Type | Description | Notes ### NewProviderTarget -`func NewProviderTarget(name string, options string, providerInfo ProviderProviderInfo, ) *ProviderTarget` +`func NewProviderTarget(isDefault bool, name string, options string, providerInfo ProviderProviderInfo, ) *ProviderTarget` NewProviderTarget instantiates a new ProviderTarget object This constructor will assign default values to properties that have it defined, @@ -27,6 +28,26 @@ NewProviderTargetWithDefaults instantiates a new ProviderTarget object This constructor will only assign default values to properties that have it defined, but it doesn't guarantee that properties required by API are set +### GetIsDefault + +`func (o *ProviderTarget) GetIsDefault() bool` + +GetIsDefault returns the IsDefault field if non-nil, zero value otherwise. + +### GetIsDefaultOk + +`func (o *ProviderTarget) GetIsDefaultOk() (*bool, bool)` + +GetIsDefaultOk returns a tuple with the IsDefault field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetIsDefault + +`func (o *ProviderTarget) SetIsDefault(v bool)` + +SetIsDefault sets IsDefault field to given value. + + ### GetName `func (o *ProviderTarget) GetName() string` diff --git a/pkg/apiclient/docs/TargetAPI.md b/pkg/apiclient/docs/TargetAPI.md index 0458163bb1..d4232dba94 100644 --- a/pkg/apiclient/docs/TargetAPI.md +++ b/pkg/apiclient/docs/TargetAPI.md @@ -6,6 +6,7 @@ Method | HTTP request | Description ------------- | ------------- | ------------- [**ListTargets**](TargetAPI.md#ListTargets) | **Get** /target | List targets [**RemoveTarget**](TargetAPI.md#RemoveTarget) | **Delete** /target/{target} | Remove a target +[**SetDefaultTarget**](TargetAPI.md#SetDefaultTarget) | **Patch** /target/{target}/set-default | Set target to default [**SetTarget**](TargetAPI.md#SetTarget) | **Put** /target | Set a target @@ -139,6 +140,74 @@ Name | Type | Description | Notes [[Back to README]](../README.md) +## SetDefaultTarget + +> SetDefaultTarget(ctx, target).Execute() + +Set target to default + + + +### Example + +```go +package main + +import ( + "context" + "fmt" + "os" + openapiclient "github.com/GIT_USER_ID/GIT_REPO_ID/apiclient" +) + +func main() { + target := "target_example" // string | Target name + + configuration := openapiclient.NewConfiguration() + apiClient := openapiclient.NewAPIClient(configuration) + r, err := apiClient.TargetAPI.SetDefaultTarget(context.Background(), target).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error when calling `TargetAPI.SetDefaultTarget``: %v\n", err) + fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) + } +} +``` + +### Path Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. +**target** | **string** | Target name | + +### Other Parameters + +Other parameters are passed through a pointer to a apiSetDefaultTargetRequest struct via the builder pattern + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + + +### Return type + + (empty response body) + +### Authorization + +[Bearer](../README.md#Bearer) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) +[[Back to Model list]](../README.md#documentation-for-models) +[[Back to README]](../README.md) + + ## SetTarget > SetTarget(ctx).Target(target).Execute() @@ -160,7 +229,7 @@ import ( ) func main() { - target := *openapiclient.NewProviderTarget("Name_example", "Options_example", *openapiclient.NewProviderProviderInfo("Name_example", "Version_example")) // ProviderTarget | Target to set + target := *openapiclient.NewCreateProviderTargetDTO("Name_example", "Options_example", *openapiclient.NewProviderProviderInfo("Name_example", "Version_example")) // CreateProviderTargetDTO | Target to set configuration := openapiclient.NewConfiguration() apiClient := openapiclient.NewAPIClient(configuration) @@ -183,7 +252,7 @@ Other parameters are passed through a pointer to a apiSetTargetRequest struct vi Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **target** | [**ProviderTarget**](ProviderTarget.md) | Target to set | + **target** | [**CreateProviderTargetDTO**](CreateProviderTargetDTO.md) | Target to set | ### Return type diff --git a/pkg/apiclient/model_create_provider_target_dto.go b/pkg/apiclient/model_create_provider_target_dto.go new file mode 100644 index 0000000000..acd7ba8273 --- /dev/null +++ b/pkg/apiclient/model_create_provider_target_dto.go @@ -0,0 +1,212 @@ +/* +Daytona Server API + +Daytona Server API + +API version: v0.0.0-dev +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package apiclient + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// checks if the CreateProviderTargetDTO type satisfies the MappedNullable interface at compile time +var _ MappedNullable = &CreateProviderTargetDTO{} + +// CreateProviderTargetDTO struct for CreateProviderTargetDTO +type CreateProviderTargetDTO struct { + Name string `json:"name"` + Options string `json:"options"` + ProviderInfo ProviderProviderInfo `json:"providerInfo"` +} + +type _CreateProviderTargetDTO CreateProviderTargetDTO + +// NewCreateProviderTargetDTO instantiates a new CreateProviderTargetDTO object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewCreateProviderTargetDTO(name string, options string, providerInfo ProviderProviderInfo) *CreateProviderTargetDTO { + this := CreateProviderTargetDTO{} + this.Name = name + this.Options = options + this.ProviderInfo = providerInfo + return &this +} + +// NewCreateProviderTargetDTOWithDefaults instantiates a new CreateProviderTargetDTO object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewCreateProviderTargetDTOWithDefaults() *CreateProviderTargetDTO { + this := CreateProviderTargetDTO{} + return &this +} + +// GetName returns the Name field value +func (o *CreateProviderTargetDTO) GetName() string { + if o == nil { + var ret string + return ret + } + + return o.Name +} + +// GetNameOk returns a tuple with the Name field value +// and a boolean to check if the value has been set. +func (o *CreateProviderTargetDTO) GetNameOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Name, true +} + +// SetName sets field value +func (o *CreateProviderTargetDTO) SetName(v string) { + o.Name = v +} + +// GetOptions returns the Options field value +func (o *CreateProviderTargetDTO) GetOptions() string { + if o == nil { + var ret string + return ret + } + + return o.Options +} + +// GetOptionsOk returns a tuple with the Options field value +// and a boolean to check if the value has been set. +func (o *CreateProviderTargetDTO) GetOptionsOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Options, true +} + +// SetOptions sets field value +func (o *CreateProviderTargetDTO) SetOptions(v string) { + o.Options = v +} + +// GetProviderInfo returns the ProviderInfo field value +func (o *CreateProviderTargetDTO) GetProviderInfo() ProviderProviderInfo { + if o == nil { + var ret ProviderProviderInfo + return ret + } + + return o.ProviderInfo +} + +// GetProviderInfoOk returns a tuple with the ProviderInfo field value +// and a boolean to check if the value has been set. +func (o *CreateProviderTargetDTO) GetProviderInfoOk() (*ProviderProviderInfo, bool) { + if o == nil { + return nil, false + } + return &o.ProviderInfo, true +} + +// SetProviderInfo sets field value +func (o *CreateProviderTargetDTO) SetProviderInfo(v ProviderProviderInfo) { + o.ProviderInfo = v +} + +func (o CreateProviderTargetDTO) MarshalJSON() ([]byte, error) { + toSerialize, err := o.ToMap() + if err != nil { + return []byte{}, err + } + return json.Marshal(toSerialize) +} + +func (o CreateProviderTargetDTO) ToMap() (map[string]interface{}, error) { + toSerialize := map[string]interface{}{} + toSerialize["name"] = o.Name + toSerialize["options"] = o.Options + toSerialize["providerInfo"] = o.ProviderInfo + return toSerialize, nil +} + +func (o *CreateProviderTargetDTO) UnmarshalJSON(data []byte) (err error) { + // This validates that all required properties are included in the JSON object + // by unmarshalling the object into a generic map with string keys and checking + // that every required field exists as a key in the generic map. + requiredProperties := []string{ + "name", + "options", + "providerInfo", + } + + allProperties := make(map[string]interface{}) + + err = json.Unmarshal(data, &allProperties) + + if err != nil { + return err + } + + for _, requiredProperty := range requiredProperties { + if _, exists := allProperties[requiredProperty]; !exists { + return fmt.Errorf("no value given for required property %v", requiredProperty) + } + } + + varCreateProviderTargetDTO := _CreateProviderTargetDTO{} + + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + err = decoder.Decode(&varCreateProviderTargetDTO) + + if err != nil { + return err + } + + *o = CreateProviderTargetDTO(varCreateProviderTargetDTO) + + return err +} + +type NullableCreateProviderTargetDTO struct { + value *CreateProviderTargetDTO + isSet bool +} + +func (v NullableCreateProviderTargetDTO) Get() *CreateProviderTargetDTO { + return v.value +} + +func (v *NullableCreateProviderTargetDTO) Set(val *CreateProviderTargetDTO) { + v.value = val + v.isSet = true +} + +func (v NullableCreateProviderTargetDTO) IsSet() bool { + return v.isSet +} + +func (v *NullableCreateProviderTargetDTO) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableCreateProviderTargetDTO(val *CreateProviderTargetDTO) *NullableCreateProviderTargetDTO { + return &NullableCreateProviderTargetDTO{value: val, isSet: true} +} + +func (v NullableCreateProviderTargetDTO) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableCreateProviderTargetDTO) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/pkg/apiclient/model_provider_target.go b/pkg/apiclient/model_provider_target.go index ff5803ec83..2d77e6a703 100644 --- a/pkg/apiclient/model_provider_target.go +++ b/pkg/apiclient/model_provider_target.go @@ -21,7 +21,8 @@ var _ MappedNullable = &ProviderTarget{} // ProviderTarget struct for ProviderTarget type ProviderTarget struct { - Name string `json:"name"` + IsDefault bool `json:"isDefault"` + Name string `json:"name"` // JSON encoded map of options Options string `json:"options"` ProviderInfo ProviderProviderInfo `json:"providerInfo"` @@ -33,8 +34,9 @@ type _ProviderTarget ProviderTarget // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewProviderTarget(name string, options string, providerInfo ProviderProviderInfo) *ProviderTarget { +func NewProviderTarget(isDefault bool, name string, options string, providerInfo ProviderProviderInfo) *ProviderTarget { this := ProviderTarget{} + this.IsDefault = isDefault this.Name = name this.Options = options this.ProviderInfo = providerInfo @@ -49,6 +51,30 @@ func NewProviderTargetWithDefaults() *ProviderTarget { return &this } +// GetIsDefault returns the IsDefault field value +func (o *ProviderTarget) GetIsDefault() bool { + if o == nil { + var ret bool + return ret + } + + return o.IsDefault +} + +// GetIsDefaultOk returns a tuple with the IsDefault field value +// and a boolean to check if the value has been set. +func (o *ProviderTarget) GetIsDefaultOk() (*bool, bool) { + if o == nil { + return nil, false + } + return &o.IsDefault, true +} + +// SetIsDefault sets field value +func (o *ProviderTarget) SetIsDefault(v bool) { + o.IsDefault = v +} + // GetName returns the Name field value func (o *ProviderTarget) GetName() string { if o == nil { @@ -131,6 +157,7 @@ func (o ProviderTarget) MarshalJSON() ([]byte, error) { func (o ProviderTarget) ToMap() (map[string]interface{}, error) { toSerialize := map[string]interface{}{} + toSerialize["isDefault"] = o.IsDefault toSerialize["name"] = o.Name toSerialize["options"] = o.Options toSerialize["providerInfo"] = o.ProviderInfo @@ -142,6 +169,7 @@ func (o *ProviderTarget) UnmarshalJSON(data []byte) (err error) { // by unmarshalling the object into a generic map with string keys and checking // that every required field exists as a key in the generic map. requiredProperties := []string{ + "isDefault", "name", "options", "providerInfo", diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 967d13b9b5..3b2cc73112 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -153,6 +153,7 @@ func ensureProfiles(cmd *cobra.Command) error { "daytona", "daytona help", "daytona docs", + "daytona generate-docs", "daytona version", "daytona profile add", "daytona serve", diff --git a/pkg/cmd/provider/install.go b/pkg/cmd/provider/install.go index dec58e96e3..f4d54e2c25 100644 --- a/pkg/cmd/provider/install.go +++ b/pkg/cmd/provider/install.go @@ -149,7 +149,7 @@ var providerInstallCmd = &cobra.Command{ return err } - targetData := apiclient.ProviderTarget{ + targetData := apiclient.CreateProviderTargetDTO{ Name: targetToSet.Name, Options: targetToSet.Options, ProviderInfo: apiclient.ProviderProviderInfo{ diff --git a/pkg/cmd/target/remove.go b/pkg/cmd/target/remove.go index e74b55c294..c8d2f0d0d4 100644 --- a/pkg/cmd/target/remove.go +++ b/pkg/cmd/target/remove.go @@ -52,7 +52,7 @@ var targetRemoveCmd = &cobra.Command{ return apiclient_util.HandleErrorResponse(res, err) } - selectedTarget, err := target.GetTargetFromPrompt(targetList, activeProfile.Name, nil, false) + selectedTarget, err := target.GetTargetFromPrompt(targetList, activeProfile.Name, nil, false, "Remove") if err != nil { if common.IsCtrlCAbort(err) { return nil @@ -74,27 +74,50 @@ var targetRemoveCmd = &cobra.Command{ return err } } else { - form := huh.NewForm( - huh.NewGroup( - huh.NewConfirm(). - Title(fmt.Sprintf("Delete all workspaces within %s?", selectedTargetName)). - Description("You might not be able to easily remove these workspaces later."). - Value(&yesFlag), - ), - ).WithTheme(views.GetCustomTheme()) - - err := form.Run() + var targetWorkspaceCount int + + workspaceList, res, err := apiClient.WorkspaceAPI.ListWorkspaces(ctx).Execute() if err != nil { - return err + return apiclient_util.HandleErrorResponse(res, err) } - if yesFlag { - err := RemoveTargetWorkspaces(ctx, apiClient, selectedTargetName) + for _, workspace := range workspaceList { + if workspace.Target == selectedTargetName { + targetWorkspaceCount++ + } + } + + if targetWorkspaceCount > 0 { + title := fmt.Sprintf("Delete %d workspaces within %s?", targetWorkspaceCount, selectedTargetName) + description := "You might not be able to easily remove these workspaces later." + + if targetWorkspaceCount == 1 { + title = fmt.Sprintf("Delete %d workspace within %s?", targetWorkspaceCount, selectedTargetName) + description = "You might not be able to easily remove this workspace later." + } + + form := huh.NewForm( + huh.NewGroup( + huh.NewConfirm(). + Title(title). + Description(description). + Value(&yesFlag), + ), + ).WithTheme(views.GetCustomTheme()) + + err := form.Run() if err != nil { return err } - } else { - fmt.Println("Proceeding with target removal without deleting workspaces.") + + if yesFlag { + err := RemoveTargetWorkspaces(ctx, apiClient, selectedTargetName) + if err != nil { + return err + } + } else { + fmt.Println("Proceeding with target removal without deleting workspaces.") + } } } diff --git a/pkg/cmd/target/set.go b/pkg/cmd/target/set.go index 103d7c9561..26fa592b51 100644 --- a/pkg/cmd/target/set.go +++ b/pkg/cmd/target/set.go @@ -116,7 +116,7 @@ var TargetSetCmd = &cobra.Command{ var selectedTarget *target_view.TargetView if !isNewProvider || len(filteredTargets) > 0 { - selectedTarget, err = target.GetTargetFromPrompt(filteredTargets, activeProfile.Name, nil, true) + selectedTarget, err = target.GetTargetFromPrompt(filteredTargets, activeProfile.Name, nil, true, "Set") if err != nil { if common.IsCtrlCAbort(err) { return nil @@ -151,7 +151,7 @@ var TargetSetCmd = &cobra.Command{ return err } - targetData := apiclient.ProviderTarget{ + targetData := apiclient.CreateProviderTargetDTO{ Name: selectedTarget.Name, Options: selectedTarget.Options, ProviderInfo: apiclient.ProviderProviderInfo{ @@ -165,7 +165,7 @@ var TargetSetCmd = &cobra.Command{ return apiclient_util.HandleErrorResponse(res, err) } - views.RenderInfoMessage("Target set successfully") + views.RenderInfoMessage("Target set successfully and will be used by default") return nil }, } diff --git a/pkg/cmd/target/setdefault.go b/pkg/cmd/target/setdefault.go new file mode 100644 index 0000000000..728b14f8ea --- /dev/null +++ b/pkg/cmd/target/setdefault.go @@ -0,0 +1,73 @@ +// Copyright 2024 Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package target + +import ( + "context" + "fmt" + + "github.com/daytonaio/daytona/cmd/daytona/config" + apiclient_util "github.com/daytonaio/daytona/internal/util/apiclient" + "github.com/daytonaio/daytona/pkg/common" + "github.com/daytonaio/daytona/pkg/views" + target_view "github.com/daytonaio/daytona/pkg/views/target" + "github.com/spf13/cobra" +) + +var targetSetDefaultCmd = &cobra.Command{ + Use: "set-default [TARGET_NAME]", + Short: "Set target to be used by default", + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) error { + var targetName string + ctx := context.Background() + + apiClient, err := apiclient_util.GetApiClient(nil) + if err != nil { + return err + } + + if len(args) == 0 { + targetList, res, err := apiClient.TargetAPI.ListTargets(ctx).Execute() + if err != nil { + return apiclient_util.HandleErrorResponse(res, err) + } + + c, err := config.GetConfig() + if err != nil { + return err + } + + activeProfile, err := c.GetActiveProfile() + if err != nil { + return err + } + + selectedTarget, err := target_view.GetTargetFromPrompt(targetList, activeProfile.Name, nil, false, "Make Default") + if err != nil { + if common.IsCtrlCAbort(err) { + return nil + } else { + return err + } + } + + if selectedTarget == nil { + return nil + } + + targetName = selectedTarget.Name + } else { + targetName = args[0] + } + + res, err := apiClient.TargetAPI.SetDefaultTarget(ctx, targetName).Execute() + if err != nil { + return apiclient_util.HandleErrorResponse(res, err) + } + + views.RenderInfoMessage(fmt.Sprintf("Target '%s' set as default", targetName)) + return nil + }, +} diff --git a/pkg/cmd/target/target.go b/pkg/cmd/target/target.go index 8d244fd5e0..2253c91081 100644 --- a/pkg/cmd/target/target.go +++ b/pkg/cmd/target/target.go @@ -18,4 +18,5 @@ func init() { TargetCmd.AddCommand(targetListCmd) TargetCmd.AddCommand(TargetSetCmd) TargetCmd.AddCommand(targetRemoveCmd) + TargetCmd.AddCommand(targetSetDefaultCmd) } diff --git a/pkg/cmd/workspace/create.go b/pkg/cmd/workspace/create.go index d0ba990d50..cd6893f410 100644 --- a/pkg/cmd/workspace/create.go +++ b/pkg/cmd/workspace/create.go @@ -48,6 +48,7 @@ var CreateCmd = &cobra.Command{ var workspaceName string var existingWorkspaceNames []string var existingProjectConfigNames []string + promptUsingTUI := len(args) == 0 apiClient, err := apiclient_util.GetApiClient(nil) if err != nil { @@ -81,7 +82,7 @@ var CreateCmd = &cobra.Command{ existingWorkspaceNames = append(existingWorkspaceNames, workspaceInfo.Name) } - if len(args) == 0 { + if promptUsingTUI { err = processPrompting(ctx, apiClient, &workspaceName, &projects, existingWorkspaceNames) if err != nil { if common.IsCtrlCAbort(err) { @@ -132,8 +133,18 @@ var CreateCmd = &cobra.Command{ return apiclient_util.HandleErrorResponse(res, err) } - target, err := workspace_util.GetTarget(ctx, apiClient, targetList, activeProfile.Name, targetNameFlag) + target, err := workspace_util.GetTarget(workspace_util.GetTargetConfig{ + Ctx: ctx, + ApiClient: apiClient, + TargetList: targetList, + ActiveProfileName: activeProfile.Name, + TargetNameFlag: targetNameFlag, + PromptUsingTUI: promptUsingTUI, + }) if err != nil { + if common.IsCtrlCAbort(err) { + return nil + } return err } @@ -519,7 +530,7 @@ func dedupProjectNames(projects *[]apiclient.CreateProjectDTO) { } func GetGitProviderGpgKey(apiClient *apiclient.APIClient, ctx context.Context, providerConfigId *string) (string, error) { - if providerConfigId == nil { + if providerConfigId == nil || *providerConfigId == "" { return "", nil } diff --git a/pkg/cmd/workspace/util/get_target.go b/pkg/cmd/workspace/util/get_target.go index 37b33605c9..49b397ed9e 100644 --- a/pkg/cmd/workspace/util/get_target.go +++ b/pkg/cmd/workspace/util/get_target.go @@ -17,17 +17,34 @@ import ( target_view "github.com/daytonaio/daytona/pkg/views/target" ) -func GetTarget(ctx context.Context, apiClient *apiclient.APIClient, targetList []apiclient.ProviderTarget, activeProfileName string, targetNameFlag string) (*target_view.TargetView, error) { - if targetNameFlag != "" { - for _, t := range targetList { - if t.Name == targetNameFlag { +type GetTargetConfig struct { + Ctx context.Context + ApiClient *apiclient.APIClient + TargetList []apiclient.ProviderTarget + ActiveProfileName string + TargetNameFlag string + PromptUsingTUI bool +} + +func GetTarget(config GetTargetConfig) (*target_view.TargetView, error) { + if config.TargetNameFlag != "" { + for _, t := range config.TargetList { + if t.Name == config.TargetNameFlag { + return util.Pointer(target_view.GetTargetViewFromTarget(t)), nil + } + } + return nil, fmt.Errorf("target '%s' not found", config.TargetNameFlag) + } + + if !config.PromptUsingTUI { + for _, t := range config.TargetList { + if t.IsDefault { return util.Pointer(target_view.GetTargetViewFromTarget(t)), nil } } - return nil, fmt.Errorf("target '%s' not found", targetNameFlag) } - serverConfig, res, err := apiClient.ServerAPI.GetConfigExecute(apiclient.ApiGetConfigRequest{}) + serverConfig, res, err := config.ApiClient.ServerAPI.GetConfigExecute(apiclient.ApiGetConfigRequest{}) if err != nil { return nil, apiclient_util.HandleErrorResponse(res, err) } @@ -48,13 +65,13 @@ func GetTarget(ctx context.Context, apiClient *apiclient.APIClient, targetList [ latestProviders := provider.GetProviderListFromManifest(providersManifestLatest) - providerViewList, err = provider.GetProviderViewOptions(apiClient, latestProviders, ctx) + providerViewList, err = provider.GetProviderViewOptions(config.ApiClient, latestProviders, config.Ctx) if err != nil { return nil, err } } - selectedTarget, err := target_view.GetTargetFromPrompt(targetList, activeProfileName, &providerViewList, false) + selectedTarget, err := target_view.GetTargetFromPrompt(config.TargetList, config.ActiveProfileName, &providerViewList, false, "Use") if err != nil { return nil, err } @@ -63,7 +80,7 @@ func GetTarget(ctx context.Context, apiClient *apiclient.APIClient, targetList [ return selectedTarget, nil } - err = provider.InstallProvider(apiClient, provider_view.ProviderView{ + err = provider.InstallProvider(config.ApiClient, provider_view.ProviderView{ Name: selectedTarget.ProviderInfo.Name, Version: selectedTarget.ProviderInfo.Version, }, providersManifest) @@ -71,13 +88,13 @@ func GetTarget(ctx context.Context, apiClient *apiclient.APIClient, targetList [ return nil, err } - targetManifest, res, err := apiClient.ProviderAPI.GetTargetManifest(context.Background(), selectedTarget.ProviderInfo.Name).Execute() + targetManifest, res, err := config.ApiClient.ProviderAPI.GetTargetManifest(context.Background(), selectedTarget.ProviderInfo.Name).Execute() if err != nil { return nil, apiclient_util.HandleErrorResponse(res, err) } selectedTarget.Name = "" - err = target_view.NewTargetNameInput(&selectedTarget.Name, util.ArrayMap(targetList, func(t apiclient.ProviderTarget) string { + err = target_view.NewTargetNameInput(&selectedTarget.Name, util.ArrayMap(config.TargetList, func(t apiclient.ProviderTarget) string { return t.Name })) if err != nil { @@ -89,7 +106,7 @@ func GetTarget(ctx context.Context, apiClient *apiclient.APIClient, targetList [ return nil, err } - res, err = apiClient.TargetAPI.SetTarget(context.Background()).Target(apiclient.ProviderTarget{ + res, err = config.ApiClient.TargetAPI.SetTarget(context.Background()).Target(apiclient.CreateProviderTargetDTO{ Name: selectedTarget.Name, Options: selectedTarget.Options, ProviderInfo: apiclient.ProviderProviderInfo{ diff --git a/pkg/cmd/workspace/util/repository_wizard.go b/pkg/cmd/workspace/util/repository_wizard.go index a43cc781de..32f74ca6d0 100644 --- a/pkg/cmd/workspace/util/repository_wizard.go +++ b/pkg/cmd/workspace/util/repository_wizard.go @@ -22,7 +22,7 @@ import ( func isGitProviderWithUnsupportedPagination(providerId string) bool { switch providerId { - case "azure-devops", "bitbucket", "gitness", "aws-codecommit": + case "azure-devops", "bitbucket", "gitness", "aws-codecommit", "gogs": return true default: return false diff --git a/pkg/db/dto/provider_target.go b/pkg/db/dto/provider_target.go index de5b5cf7cc..53f03752e6 100644 --- a/pkg/db/dto/provider_target.go +++ b/pkg/db/dto/provider_target.go @@ -11,6 +11,7 @@ type ProviderTargetDTO struct { ProviderLabel *string `json:"providerLabel,omitempty"` ProviderVersion string `json:"providerVersion"` Options string `json:"options"` + IsDefault bool `json:"isDefault"` } func ToProviderTargetDTO(providerTarget *provider.ProviderTarget) ProviderTargetDTO { @@ -20,6 +21,7 @@ func ToProviderTargetDTO(providerTarget *provider.ProviderTarget) ProviderTarget ProviderLabel: providerTarget.ProviderInfo.Label, ProviderVersion: providerTarget.ProviderInfo.Version, Options: providerTarget.Options, + IsDefault: providerTarget.IsDefault, } } @@ -31,6 +33,7 @@ func ToProviderTarget(providerTargetDTO ProviderTargetDTO) *provider.ProviderTar Label: providerTargetDTO.ProviderLabel, Version: providerTargetDTO.ProviderVersion, }, - Options: providerTargetDTO.Options, + Options: providerTargetDTO.Options, + IsDefault: providerTargetDTO.IsDefault, } } diff --git a/pkg/db/provider_target_store.go b/pkg/db/provider_target_store.go index fda1d0f790..a0efda39d8 100644 --- a/pkg/db/provider_target_store.go +++ b/pkg/db/provider_target_store.go @@ -23,24 +23,26 @@ func NewProviderTargetStore(db *gorm.DB) (*ProviderTargetStore, error) { return &ProviderTargetStore{db: db}, nil } -func (s *ProviderTargetStore) List() ([]*provider.ProviderTarget, error) { - providerTargetsDTOs := []ProviderTargetDTO{} - tx := s.db.Find(&providerTargetsDTOs) +func (s *ProviderTargetStore) List(filter *provider.TargetFilter) ([]*provider.ProviderTarget, error) { + targetDTOs := []ProviderTargetDTO{} + tx := processTargetFilters(s.db, filter).Find(&targetDTOs) + if tx.Error != nil { return nil, tx.Error } - providerTargets := []*provider.ProviderTarget{} - for _, providerTargetDTO := range providerTargetsDTOs { - providerTargets = append(providerTargets, ToProviderTarget(providerTargetDTO)) + targets := []*provider.ProviderTarget{} + for _, targetDTO := range targetDTOs { + targets = append(targets, ToProviderTarget(targetDTO)) } - return providerTargets, nil + return targets, nil } -func (s *ProviderTargetStore) Find(targetName string) (*provider.ProviderTarget, error) { - providerTargetDTO := ProviderTargetDTO{} - tx := s.db.Where("name = ?", targetName).First(&providerTargetDTO) +func (s *ProviderTargetStore) Find(filter *provider.TargetFilter) (*provider.ProviderTarget, error) { + targetDTO := ProviderTargetDTO{} + tx := processTargetFilters(s.db, filter).First(&targetDTO) + if tx.Error != nil { if IsRecordNotFound(tx.Error) { return nil, provider.ErrTargetNotFound @@ -48,7 +50,7 @@ func (s *ProviderTargetStore) Find(targetName string) (*provider.ProviderTarget, return nil, tx.Error } - return ToProviderTarget(providerTargetDTO), nil + return ToProviderTarget(targetDTO), nil } func (s *ProviderTargetStore) Save(target *provider.ProviderTarget) error { @@ -71,3 +73,16 @@ func (s *ProviderTargetStore) Delete(target *provider.ProviderTarget) error { return nil } + +func processTargetFilters(tx *gorm.DB, filter *provider.TargetFilter) *gorm.DB { + if filter != nil { + if filter.Name != nil { + tx = tx.Where("name = ?", *filter.Name) + } + if filter.Default != nil { + tx = tx.Where("is_default = ?", *filter.Default) + } + } + + return tx +} diff --git a/pkg/git/service.go b/pkg/git/service.go index 0b4aa29345..1c66db81ab 100644 --- a/pkg/git/service.go +++ b/pkg/git/service.go @@ -100,18 +100,19 @@ func (s *Service) CloneRepository(repo *gitprovider.GitRepository, auth *http.Ba } func (s *Service) CloneRepositoryCmd(repo *gitprovider.GitRepository, auth *http.BasicAuth) []string { - branch := fmt.Sprintf("\"%s\"", repo.Branch) - cloneCmd := []string{"git", "clone", "--single-branch", "--branch", branch} + cloneCmd := []string{"git", "clone", "--single-branch", "--branch", fmt.Sprintf("\"%s\"", repo.Branch)} + cloneUrl := repo.Url + + // Default to https protocol if not specified + if !strings.Contains(cloneUrl, "://") { + cloneUrl = fmt.Sprintf("https://%s", cloneUrl) + } if auth != nil { - repoUrl := strings.TrimPrefix(repo.Url, "https://") - repoUrl = strings.TrimPrefix(repoUrl, "http://") - cloneCmd = append(cloneCmd, fmt.Sprintf("https://%s:%s@%s", auth.Username, auth.Password, repoUrl)) - } else { - cloneCmd = append(cloneCmd, repo.Url) + cloneUrl = fmt.Sprintf("%s://%s:%s@%s", strings.Split(cloneUrl, "://")[0], auth.Username, auth.Password, strings.SplitN(cloneUrl, "://", 2)[1]) } - cloneCmd = append(cloneCmd, s.ProjectDir) + cloneCmd = append(cloneCmd, cloneUrl, s.ProjectDir) if repo.Target == gitprovider.CloneTargetCommit { cloneCmd = append(cloneCmd, "&&", "cd", s.ProjectDir) diff --git a/pkg/git/service_test.go b/pkg/git/service_test.go new file mode 100644 index 0000000000..f1d7ad4b26 --- /dev/null +++ b/pkg/git/service_test.go @@ -0,0 +1,98 @@ +// Copyright 2024 Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package git_test + +import ( + "testing" + + "github.com/daytonaio/daytona/pkg/git" + "github.com/daytonaio/daytona/pkg/gitprovider" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/stretchr/testify/suite" +) + +var repoHttp = &gitprovider.GitRepository{ + Id: "123", + Url: "http://localhost:3000/daytonaio/daytona", + Name: "daytona", + Branch: "main", + Target: gitprovider.CloneTargetBranch, +} + +var repoHttps = &gitprovider.GitRepository{ + Id: "123", + Url: "https://github.com/daytonaio/daytona", + Name: "daytona", + Branch: "main", + Target: gitprovider.CloneTargetBranch, +} + +var repoWithoutProtocol = &gitprovider.GitRepository{ + Id: "123", + Url: "github.com/daytonaio/daytona", + Name: "daytona", + Branch: "main", + Target: gitprovider.CloneTargetBranch, +} + +var repoWithCloneTargetCommit = &gitprovider.GitRepository{ + Id: "123", + Url: "https://github.com/daytonaio/daytona", + Name: "daytona", + Branch: "main", + Sha: "1234567890", + Target: gitprovider.CloneTargetCommit, +} + +var creds = &http.BasicAuth{ + Username: "daytonaio", + Password: "Daytona123", +} + +type GitServiceTestSuite struct { + suite.Suite + gitService git.IGitService +} + +func NewGitServiceTestSuite() *GitServiceTestSuite { + return &GitServiceTestSuite{} +} + +func (s *GitServiceTestSuite) SetupTest() { + s.gitService = &git.Service{ + ProjectDir: "/workdir", + } +} + +func TestGitService(t *testing.T) { + suite.Run(t, NewGitServiceTestSuite()) +} + +func (s *GitServiceTestSuite) TestCloneRepositoryCmd_WithCreds() { + cloneCmd := s.gitService.CloneRepositoryCmd(repoHttps, creds) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "https://daytonaio:Daytona123@github.com/daytonaio/daytona", "/workdir"}, cloneCmd) + + cloneCmd = s.gitService.CloneRepositoryCmd(repoHttp, creds) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "http://daytonaio:Daytona123@localhost:3000/daytonaio/daytona", "/workdir"}, cloneCmd) + + cloneCmd = s.gitService.CloneRepositoryCmd(repoWithoutProtocol, creds) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "https://daytonaio:Daytona123@github.com/daytonaio/daytona", "/workdir"}, cloneCmd) + + cloneCmd = s.gitService.CloneRepositoryCmd(repoWithCloneTargetCommit, creds) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "https://daytonaio:Daytona123@github.com/daytonaio/daytona", "/workdir", "&&", "cd", "/workdir", "&&", "git", "checkout", "1234567890"}, cloneCmd) +} + +func (s *GitServiceTestSuite) TestCloneRepositoryCmd_WithoutCreds() { + cloneCmd := s.gitService.CloneRepositoryCmd(repoHttps, nil) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "https://github.com/daytonaio/daytona", "/workdir"}, cloneCmd) + + cloneCmd = s.gitService.CloneRepositoryCmd(repoHttp, nil) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "http://localhost:3000/daytonaio/daytona", "/workdir"}, cloneCmd) + + cloneCmd = s.gitService.CloneRepositoryCmd(repoWithoutProtocol, nil) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "https://github.com/daytonaio/daytona", "/workdir"}, cloneCmd) + + cloneCmd = s.gitService.CloneRepositoryCmd(repoWithCloneTargetCommit, nil) + s.Require().Equal([]string{"git", "clone", "--single-branch", "--branch", "\"main\"", "https://github.com/daytonaio/daytona", "/workdir", "&&", "cd", "/workdir", "&&", "git", "checkout", "1234567890"}, cloneCmd) +} diff --git a/pkg/gitprovider/gitness.go b/pkg/gitprovider/gitness.go index ddf89a3963..7676c480ef 100644 --- a/pkg/gitprovider/gitness.go +++ b/pkg/gitprovider/gitness.go @@ -4,12 +4,16 @@ package gitprovider import ( + "encoding/json" "fmt" + "io" + "math" "net/http" "net/url" "strconv" "strings" + "github.com/daytonaio/daytona/internal/util" gitnessclient "github.com/daytonaio/daytona/pkg/gitprovider/gitnessclient" ) @@ -48,7 +52,7 @@ func (g *GitnessGitProvider) GetNamespaces(options ListOptions) ([]*GitNamespace var namespaces []*GitNamespace for _, membership := range response { namespace := &GitNamespace{ - Id: membership.Space.UID, + Id: membership.Space.Identifier, Name: membership.Space.Identifier, } namespaces = append(namespaces, namespace) @@ -77,6 +81,7 @@ func (g *GitnessGitProvider) GetRepositories(namespace string, options ListOptio if err != nil { return nil, err } + repo := &GitRepository{ Id: repo.Identifier, Name: repo.Identifier, @@ -162,7 +167,6 @@ func (g *GitnessGitProvider) GetUrlFromContext(repoContext *GetRepositoryContext func (g *GitnessGitProvider) GetBranchByCommit(staticContext *StaticGitContext) (string, error) { client := g.getApiClient() - response, err := client.GetRepoBranches(staticContext.Name, staticContext.Owner) if err != nil { return "", fmt.Errorf("failed to fetch Branches: %w", err) @@ -204,7 +208,7 @@ func (g *GitnessGitProvider) GetBranchByCommit(staticContext *StaticGitContext) func (g *GitnessGitProvider) GetLastCommitSha(staticContext *StaticGitContext) (string, error) { client := g.getApiClient() - return client.GetLastCommitSha(staticContext.Url, staticContext.Branch) + return client.GetLastCommitSha(staticContext.Owner, staticContext.Name, staticContext.Branch) } func (g *GitnessGitProvider) GetPrContext(staticContext *StaticGitContext) (*StaticGitContext, error) { @@ -237,6 +241,18 @@ func (g *GitnessGitProvider) ParseStaticGitContext(repoUrl string) (*StaticGitCo if err != nil { return nil, err } + if strings.Contains(repoUrl, "/git/") { + ref, err := g.getApiClient().GetRepoRef(repoUrl) + if err != nil { + return nil, err + } + refParts := strings.Split(*ref, "/") + staticContext.Owner = refParts[0] + staticContext.Name = refParts[1] + staticContext.Id = refParts[1] + staticContext.Path = nil + } + parsedUrl, err := url.Parse(repoUrl) if err != nil { return nil, err @@ -245,6 +261,7 @@ func (g *GitnessGitProvider) ParseStaticGitContext(repoUrl string) (*StaticGitCo if staticContext.Path == nil { return staticContext, nil } + parts := strings.Split(*staticContext.Path, "/") switch { @@ -281,3 +298,96 @@ func (g *GitnessGitProvider) GetDefaultBranch(staticContext *StaticGitContext) ( client := g.getApiClient() return client.GetDefaultBranch(staticContext.Url) } + +func (g *GitnessGitProvider) RegisterPrebuildWebhook(repo *GitRepository, endpointUrl string) (string, error) { + client := g.getApiClient() + webhook, err := client.CreateWebhook(repo.Id, repo.Owner, gitnessclient.Webhook{ + Triggers: []string{"branch_updated"}, + Url: endpointUrl, + Identifier: "daytona-webhook_" + repo.Id, + DisplayName: "Daytona Webhook", + Enabled: true, + }) + if err != nil { + return "", err + } + return webhook.Uid, nil +} + +func (g *GitnessGitProvider) GetPrebuildWebhook(repo *GitRepository, endpointUrl string) (*string, error) { + client := g.getApiClient() + webhooks, err := client.GetAllWebhooks(repo.Id, repo.Owner) + if err != nil { + return nil, err + } + for _, webhook := range webhooks { + if webhook.Url == endpointUrl { + return &webhook.Uid, nil + } + } + return nil, nil +} + +func (g *GitnessGitProvider) UnregisterPrebuildWebhook(repo *GitRepository, id string) error { + client := g.getApiClient() + return client.DeleteWebhook(repo.Id, repo.Owner, id) +} + +func (g *GitnessGitProvider) GetCommitsRange(repo *GitRepository, initialSha string, currentSha string) (int, error) { + client := g.getApiClient() + commits, err := client.GetCommits(repo.Owner, repo.Name, &repo.Branch) + if err != nil { + return 0, err + } + initialShaIndex := -1 + currentShaIndex := -1 + for i := 0; i < len(*commits); i++ { + if currentSha == (*commits)[i].Sha { + currentShaIndex = i + } + if initialSha == (*commits)[i].Sha { + initialShaIndex = i + } + if initialShaIndex != -1 && currentShaIndex != -1 { + break + } + } + + if initialShaIndex == -1 || currentShaIndex == -1 { + return 0, fmt.Errorf("Sha Not found in commits") + } + + commitLength := int(math.Abs(float64(initialShaIndex - currentShaIndex))) + + return commitLength, nil +} + +func (g *GitnessGitProvider) ParseEventData(request *http.Request) (*GitEventData, error) { + payload, err := io.ReadAll(request.Body) + if err != nil { + return nil, err + } + var webhookEvent gitnessclient.WebhookEventData + err = json.Unmarshal(payload, &webhookEvent) + if err != nil { + return nil, err + } + + if webhookEvent.Trigger != "branch_updated" { + return nil, nil + } + + gitEventData := &GitEventData{ + Url: util.CleanUpRepositoryUrl(webhookEvent.Repo.GitURL), + Branch: strings.TrimPrefix(webhookEvent.Ref.Name, "refs/heads/"), + Sha: webhookEvent.Sha, + Owner: webhookEvent.Principal.DisplayName, + } + + for _, commit := range webhookEvent.Commits { + gitEventData.AffectedFiles = append(gitEventData.AffectedFiles, commit.Modified...) + gitEventData.AffectedFiles = append(gitEventData.AffectedFiles, commit.Added...) + gitEventData.AffectedFiles = append(gitEventData.AffectedFiles, commit.Removed...) + } + return gitEventData, nil +} diff --git a/pkg/gitprovider/gitness_test.go b/pkg/gitprovider/gitness_test.go index cd0560f2a4..10f30ff840 100644 --- a/pkg/gitprovider/gitness_test.go +++ b/pkg/gitprovider/gitness_test.go @@ -34,6 +34,26 @@ func (g *GitnessGitProviderTestSuite) TestCanHandle_False() { require.False(canHandle) } +func (g *GitnessGitProviderTestSuite) TestParseStaticGitContext_GIT_URL() { + gitUrl := "https://localhost:3000/git/test/test.git" + gitContext := &StaticGitContext{ + Id: "test", + Name: "test", + Owner: "test", + Url: "https://localhost:3000/git/test/test.git", + Branch: nil, + Sha: nil, + Source: "localhost:3000", + Path: nil, + PrNumber: nil, + } + + require := g.Require() + httpContext, err := g.gitProvider.ParseStaticGitContext(gitUrl) + require.Nil(err) + require.Equal(gitContext, httpContext) +} + func (g *GitnessGitProviderTestSuite) TestParseStaticGitContext_PR() { prUrl := "https://localhost:3000/test/test/pulls/1" prContext := &StaticGitContext{ diff --git a/pkg/gitprovider/gitnessclient/gitness_client.go b/pkg/gitprovider/gitnessclient/gitness_client.go index 7f15836c44..b75fb9d625 100644 --- a/pkg/gitprovider/gitnessclient/gitness_client.go +++ b/pkg/gitprovider/gitnessclient/gitness_client.go @@ -4,6 +4,7 @@ package gitnessclient import ( + "bytes" "context" "encoding/json" "errors" @@ -50,7 +51,7 @@ func (g *GitnessClient) performRequest(method, requestURL string) ([]byte, error return nil, err } - if res.StatusCode != http.StatusOK { + if res.StatusCode >= 400 { return nil, fmt.Errorf("status code: %d err: %s", res.StatusCode, string(body)) } @@ -68,8 +69,6 @@ func (g *GitnessClient) GetCommits(owner string, repositoryName string, branch * v := url.Values{} v.Add("git_ref", *branch) apiURL = api.String() + "?" + v.Encode() - } else { - apiURL = api.String() } body, err := g.performRequest("GET", apiURL) @@ -189,13 +188,13 @@ func (g *GitnessClient) GetRepositories(namespace string) ([]Repository, error) return apiRepos, nil } -func (g *GitnessClient) GetRepository(url string) (*Repository, error) { - repoRef, err := g.GetRepoRef(url) +func (g *GitnessClient) GetRepository(repoUrl string) (*Repository, error) { + repoRef, err := g.GetRepoRef(repoUrl) if err != nil { return nil, err } - repoURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s", *repoRef)) + repoURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s", url.PathEscape(*repoRef))) if err != nil { return nil, err } @@ -214,7 +213,7 @@ func (g *GitnessClient) GetRepository(url string) (*Repository, error) { } func (g *GitnessClient) GetRepoBranches(repositoryId string, namespaceId string) ([]*RepoBranch, error) { - branchesURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/branches", url.PathEscape(namespaceId+"/"+repositoryId))) + branchesURL, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/branches", url.PathEscape(fmt.Sprintf("%s/%s", namespaceId, repositoryId)))) if err != nil { return nil, err } @@ -254,12 +253,8 @@ func (g *GitnessClient) GetRepoPRs(repositoryId string, namespaceId string) ([]* return apiPRs, nil } -func (g *GitnessClient) GetLastCommitSha(repoURL string, branch *string) (string, error) { - ref, err := g.GetRepoRef(repoURL) - if err != nil { - return "", err - } - api, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/commits", url.PathEscape(*ref))) +func (g *GitnessClient) GetLastCommitSha(owner string, repositoryName string, branch *string) (string, error) { + api, err := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/commits", url.PathEscape(fmt.Sprintf("%s/%s", owner, repositoryName)))) if err != nil { return "", fmt.Errorf("failed to parse url : %w", err) } @@ -286,18 +281,15 @@ func (g *GitnessClient) GetLastCommitSha(repoURL string, branch *string) (string return lastCommit.Sha, nil } -func (g *GitnessClient) GetRepoRef(url string) (*string, error) { - repoUrl := strings.TrimSuffix(url, ".git") - parts := strings.Split(repoUrl, "/") - if len(parts) < 5 { - return nil, errors.New("failed to parse repository reference: invalid url passed") - } - var path string - if parts[3] == "git" && len(parts) >= 6 { - path = fmt.Sprintf("%s/%s", parts[4], parts[5]) - } else { - path = fmt.Sprintf("%s/%s", parts[3], parts[4]) +func (g *GitnessClient) GetRepoRef(gitUrl string) (*string, error) { + repoUrl := strings.TrimSuffix(gitUrl, ".git") + parsedRepoUrl, err := url.Parse(repoUrl) + if err != nil { + return nil, err } + parsedRepoUrl.Path = strings.TrimPrefix(parsedRepoUrl.Path, "/git/") + parts := strings.Split(parsedRepoUrl.Path, "/") + path := fmt.Sprintf("%s/%s", parts[0], parts[1]) return &path, nil } @@ -353,6 +345,82 @@ func (g *GitnessClient) GetDefaultBranch(url string) (*string, error) { return &repo.DefaultBranch, nil } +func (g *GitnessClient) CreateWebhook(repoId string, namespaceId string, webhook Webhook) (*Webhook, error) { + webhookEndpoint, parseErr := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/webhooks", url.PathEscape(namespaceId+"/"+repoId))) + if parseErr != nil { + return nil, parseErr + } + + jsonData, err := json.Marshal(webhook) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(context.Background(), "POST", webhookEndpoint.String(), bytes.NewBuffer(jsonData)) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", "Bearer "+g.token) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("status code: %d err: %s", resp.StatusCode, string(responseData)) + } + + var newWebhook Webhook + if err := json.Unmarshal(responseData, &newWebhook); err != nil { + return nil, err + } + + return &newWebhook, nil +} + +func (g *GitnessClient) DeleteWebhook(repoID string, namespaceId string, webhookID string) error { + webhookEndpoint, parseErr := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/webhooks/%s", url.PathEscape(namespaceId+"/"+repoID), webhookID)) + if parseErr != nil { + return parseErr + } + _, reqErr := g.performRequest("DELETE", webhookEndpoint.String()) + if reqErr != nil { + return reqErr + } + + return nil +} + +func (g *GitnessClient) GetAllWebhooks(repoId string, namespaceId string) ([]*Webhook, error) { + webhookEndpoint, parseErr := g.BaseURL.Parse(fmt.Sprintf("/api/v1/repos/%s/webhooks", url.PathEscape(namespaceId+"/"+repoId))) + if parseErr != nil { + return nil, parseErr + } + + responseData, reqErr := g.performRequest("GET", webhookEndpoint.String()) + if reqErr != nil { + return nil, reqErr + } + + var webhookList []*Webhook + if unmarshalErr := json.Unmarshal(responseData, &webhookList); unmarshalErr != nil { + return nil, unmarshalErr + } + + return webhookList, nil +} + func GetCloneUrl(protocol, host, owner, repo string) string { return fmt.Sprintf("%s://%s/git/%s/%s.git", protocol, host, owner, repo) } diff --git a/pkg/gitprovider/gitnessclient/gitness_types.go b/pkg/gitprovider/gitnessclient/gitness_types.go index 30aa5f1269..8f3d1e6102 100644 --- a/pkg/gitprovider/gitnessclient/gitness_types.go +++ b/pkg/gitprovider/gitnessclient/gitness_types.go @@ -186,3 +186,127 @@ type Repository struct { GitUrl string `json:"git_url"` Uid string `json:"uid"` } + +type Webhook struct { + Id int64 `json:"id"` + Version int64 `json:"version"` + ParentID int64 `json:"parent_id"` + ParentType string `json:"parent_type"` + CreatedBy int64 `json:"created_by"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` + Identifier string `json:"identifier"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + Url string `json:"url"` + Enabled bool `json:"enabled"` + Insecure bool `json:"insecure"` + Triggers []string `json:"triggers"` + HasSecret bool `json:"has_secret"` + Uid string `json:"uid"` +} + +type WebhookEventData struct { + Commit struct { + Added []string `json:"added"` + Author struct { + Identity struct { + Email string `json:"email"` + Name string `json:"name"` + } `json:"identity"` + When time.Time `json:"when"` + } `json:"author"` + Committer struct { + Identity struct { + Email string `json:"email"` + Name string `json:"name"` + } `json:"identity"` + When time.Time `json:"when"` + } `json:"committer"` + Message string `json:"message"` + Modified []string `json:"modified"` + Removed []string `json:"removed"` + Sha string `json:"sha"` + } `json:"commit"` + Commits []struct { + Added []string `json:"added"` + Author struct { + Identity struct { + Email string `json:"email"` + Name string `json:"name"` + } `json:"identity"` + When time.Time `json:"when"` + } `json:"author"` + Committer struct { + Identity struct { + Email string `json:"email"` + Name string `json:"name"` + } `json:"identity"` + When time.Time `json:"when"` + } `json:"committer"` + Message string `json:"message"` + Modified []string `json:"modified"` + Removed []string `json:"removed"` + Sha string `json:"sha"` + } `json:"commits"` + Forced bool `json:"forced"` + HeadCommit struct { + Added []string `json:"added"` + Author struct { + Identity struct { + Email string `json:"email"` + Name string `json:"name"` + } `json:"identity"` + When time.Time `json:"when"` + } `json:"author"` + Committer struct { + Identity struct { + Email string `json:"email"` + Name string `json:"name"` + } `json:"identity"` + When time.Time `json:"when"` + } `json:"committer"` + Message string `json:"message"` + Modified []string `json:"modified"` + Removed []string `json:"removed"` + Sha string `json:"sha"` + } `json:"head_commit"` + OldSha string `json:"old_sha"` + Principal struct { + Created int64 `json:"created"` + DisplayName string `json:"display_name"` + Email string `json:"email"` + ID int `json:"id"` + Type string `json:"type"` + UID string `json:"uid"` + Updated int64 `json:"updated"` + } `json:"principal"` + Ref struct { + Name string `json:"name"` + Repo struct { + DefaultBranch string `json:"default_branch"` + Description string `json:"description"` + GitSSHURL string `json:"git_ssh_url"` + GitURL string `json:"git_url"` + ID int `json:"id"` + Identifier string `json:"identifier"` + Path string `json:"path"` + UID string `json:"uid"` + URL string `json:"url"` + } `json:"repo"` + } `json:"ref"` + Repo struct { + DefaultBranch string `json:"default_branch"` + Description string `json:"description"` + GitSSHURL string `json:"git_ssh_url"` + GitURL string `json:"git_url"` + ID int `json:"id"` + Identifier string `json:"identifier"` + Path string `json:"path"` + UID string `json:"uid"` + URL string `json:"url"` + } `json:"repo"` + Sha string `json:"sha"` + TotalCommitsCount int `json:"total_commits_count"` + Trigger string `json:"trigger"` +} diff --git a/pkg/gitprovider/gogs.go b/pkg/gitprovider/gogs.go new file mode 100644 index 0000000000..e582f0f884 --- /dev/null +++ b/pkg/gitprovider/gogs.go @@ -0,0 +1,287 @@ +// Copyright 2024 Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package gitprovider + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + gogs "github.com/gogs/go-gogs-client" +) + +type GogsGitProvider struct { + *AbstractGitProvider + + token string + baseApiUrl string +} + +func NewGogsGitProvider(token string, baseApiUrl string) *GogsGitProvider { + provider := &GogsGitProvider{ + token: token, + baseApiUrl: baseApiUrl, + AbstractGitProvider: &AbstractGitProvider{}, + } + provider.AbstractGitProvider.GitProvider = provider + + return provider +} + +func (g *GogsGitProvider) getApiClient() *gogs.Client { + return gogs.NewClient(g.baseApiUrl, g.token) +} + +func (g *GogsGitProvider) CanHandle(repoUrl string) (bool, error) { + staticContext, err := g.ParseStaticGitContext(repoUrl) + if err != nil { + return false, err + } + + return strings.Contains(g.baseApiUrl, staticContext.Source), nil +} + +func (g *GogsGitProvider) GetNamespaces(options ListOptions) ([]*GitNamespace, error) { + client := g.getApiClient() + var namespaces []*GitNamespace + user, err := g.GetUser() + if err != nil { + return nil, err + } + orgs, err := client.ListMyOrgs() + if err != nil { + return nil, fmt.Errorf("failed to fetch Namespace : %w", err) + } + + namespaces = append([]*GitNamespace{{Id: personalNamespaceId, Name: user.Username}}, namespaces...) + for _, org := range orgs { + namespaces = append(namespaces, &GitNamespace{Id: org.UserName, Name: org.UserName}) + } + return namespaces, nil +} + +func (g *GogsGitProvider) GetUser() (*GitUser, error) { + client := g.getApiClient() + user, err := client.GetSelfInfo() + if err != nil { + return nil, fmt.Errorf("failed to fetch User : %w", err) + } + fullName := user.FullName + userName := user.UserName + if fullName == "" { + fullName = userName + } + return &GitUser{ + Id: strconv.FormatInt(user.ID, 10), + Username: user.UserName, + Name: fullName, + Email: user.Email, + }, nil +} + +func (g *GogsGitProvider) GetRepositories(namespace string, options ListOptions) ([]*GitRepository, error) { + client := g.getApiClient() + var repoList []*gogs.Repository + if namespace == personalNamespaceId { + repos, err := client.ListMyRepos() + if err != nil { + return nil, fmt.Errorf("failed to fetch Repositories : %w", err) + } + repoList = repos + } else { + repos, err := client.ListOrgRepos(namespace) + if err != nil { + return nil, fmt.Errorf("failed to fetch Repositories : %w", err) + } + repoList = repos + } + repos := []*GitRepository{} + for _, repo := range repoList { + u, err := url.Parse(repo.HTMLURL) + if err != nil { + return nil, err + } + + repos = append(repos, &GitRepository{ + Id: repo.Name, + Name: repo.Name, + Url: repo.HTMLURL, + Branch: repo.DefaultBranch, + Owner: repo.Owner.UserName, + Source: u.Host, + }) + } + + return repos, nil +} + +func (g *GogsGitProvider) GetRepoBranches(repositoryId string, namespaceId string, options ListOptions) ([]*GitBranch, error) { + client := g.getApiClient() + if namespaceId == personalNamespaceId { + user, err := g.GetUser() + if err != nil { + return nil, err + } + namespaceId = user.Username + } + var branches []*GitBranch + + repoBranches, err := client.ListRepoBranches(namespaceId, repositoryId) + if err != nil { + return nil, fmt.Errorf("failed to fetch Branches: %w", err) + } + for _, branch := range repoBranches { + responseBranch := &GitBranch{ + Name: branch.Name, + } + if branch.Commit != nil { + responseBranch.Sha = branch.Commit.ID + } + branches = append(branches, responseBranch) + } + + return branches, nil +} + +func (g *GogsGitProvider) GetDefaultBranch(staticContext *StaticGitContext) (*string, error) { + client := g.getApiClient() + repo, err := client.GetRepo(staticContext.Owner, staticContext.Name) + if err != nil { + return nil, err + } + + return &repo.DefaultBranch, nil +} + +func (g *GogsGitProvider) GetRepoPRs(repositoryId string, namespaceId string, options ListOptions) ([]*GitPullRequest, error) { + // Gogs does not have any API endpoint to fetch PRs + return []*GitPullRequest{}, nil +} + +func (g *GogsGitProvider) GetBranchByCommit(staticContext *StaticGitContext) (string, error) { + client := g.getApiClient() + repoBranches, err := client.ListRepoBranches(staticContext.Owner, staticContext.Name) + if err != nil { + return "", fmt.Errorf("failed to get branch by commit: %w", err) + } + + var branchName string + for _, branch := range repoBranches { + commitId := branch.Commit.ID + if *staticContext.Sha == commitId { + branchName = branch.Name + break + } + + for commitId != "" { + commit, err := client.GetSingleCommit(staticContext.Owner, staticContext.Name, commitId) + if err != nil { + continue + } + + if *staticContext.Sha == commit.SHA { + branchName = branch.Name + break + } + if len(commit.Parents) > 0 { + commitId = commit.Parents[0].SHA + if *staticContext.Sha == commitId { + branchName = branch.Name + break + } + } else { + commitId = "" + } + } + + if branchName != "" { + break + } + } + + if branchName == "" { + return "", fmt.Errorf("status code: %d branch not found for SHA: %s", http.StatusNotFound, *staticContext.Sha) + } + return branchName, nil +} + +func (g *GogsGitProvider) GetLastCommitSha(staticContext *StaticGitContext) (string, error) { + client := g.getApiClient() + branchName := "" + if staticContext.Branch != nil { + branchName = *staticContext.Branch + } + branch, err := client.GetRepoBranch(staticContext.Owner, staticContext.Name, branchName) + if err != nil { + return "", fmt.Errorf("failed to get last commit SHA: %w", err) + } + return branch.Commit.ID, nil +} + +func (g *GogsGitProvider) ParseStaticGitContext(repoUrl string) (*StaticGitContext, error) { + staticContext, err := g.AbstractGitProvider.ParseStaticGitContext(repoUrl) + if err != nil { + return nil, err + } + + if staticContext.Path == nil { + return staticContext, nil + } + + parts := strings.Split(*staticContext.Path, "/") + switch { + case len(parts) >= 2 && parts[0] == "pulls": + prNumber, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, err + } + prUint := uint32(prNumber) + staticContext.PrNumber = &prUint + staticContext.Path = nil + case len(parts) >= 2 && parts[0] == "src": + staticContext.Branch = &parts[1] + if len(parts) > 2 { + branchPath := strings.Join(parts[2:], "/") + staticContext.Path = &branchPath + } else { + staticContext.Path = nil + } + case len(parts) >= 2 && parts[0] == "commits": + staticContext.Branch = &parts[1] + staticContext.Path = nil + case len(parts) >= 2 && parts[0] == "commit": + staticContext.Sha = &parts[1] + staticContext.Branch = staticContext.Sha + staticContext.Path = nil + } + + return staticContext, nil +} + +func (g *GogsGitProvider) GetPrContext(staticContext *StaticGitContext) (*StaticGitContext, error) { + // Gogs does not have any API endpoint to fetch PRs + return nil, fmt.Errorf("creating workspaces from Pull Requests is not supported for Gogs") +} + +func (g *GogsGitProvider) GetUrlFromContext(repoContext *GetRepositoryContext) string { + url := strings.TrimSuffix(repoContext.Url, ".git") + + if repoContext.Branch != nil && *repoContext.Branch != "" { + if repoContext.Sha != nil && *repoContext.Sha == *repoContext.Branch { + url += "/commit/" + *repoContext.Branch + } else { + url += "/src/" + *repoContext.Branch + } + + if repoContext.Path != nil && *repoContext.Path != "" { + url += "/" + *repoContext.Path + } + } else if repoContext.Path != nil { + url += "/src/main/" + *repoContext.Path + } + + return url +} diff --git a/pkg/gitprovider/gogs_test.go b/pkg/gitprovider/gogs_test.go new file mode 100644 index 0000000000..8327489fa0 --- /dev/null +++ b/pkg/gitprovider/gogs_test.go @@ -0,0 +1,232 @@ +// Copyright 2024 Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package gitprovider + +import ( + "testing" + + "github.com/daytonaio/daytona/internal/util" + "github.com/stretchr/testify/suite" +) + +type GogsGitProviderTestSuite struct { + gitProvider *GogsGitProvider + suite.Suite +} + +func NewGogsGitProviderTestSuite() *GogsGitProviderTestSuite { + return &GogsGitProviderTestSuite{ + gitProvider: NewGogsGitProvider("", "https://gogs-host.com"), + } +} + +func (g *GogsGitProviderTestSuite) TestCanHandle() { + repoUrl := "https://gogs-host.com/daytonaio/daytona" + require := g.Require() + canHandle, _ := g.gitProvider.CanHandle(repoUrl) + require.True(canHandle) +} + +func (g *GogsGitProviderTestSuite) TestCanHandle_False() { + repoUrl := "https://github.com/daytonaio/daytona" + require := g.Require() + canHandle, _ := g.gitProvider.CanHandle(repoUrl) + require.False(canHandle) +} + +func (g *GogsGitProviderTestSuite) TestParseStaticGitContext_PR() { + prUrl := "https://gogs-host.com/daytonaio/daytona/pulls/1" + prContext := &StaticGitContext{ + Id: "daytona", + Name: "daytona", + Owner: "daytonaio", + Url: "https://gogs-host.com/daytonaio/daytona.git", + Source: "gogs-host.com", + Branch: nil, + Sha: nil, + PrNumber: util.Pointer(uint32(1)), + Path: nil, + } + + require := g.Require() + + httpContext, err := g.gitProvider.ParseStaticGitContext(prUrl) + + require.Nil(err) + require.Equal(httpContext, prContext) +} + +func (g *GogsGitProviderTestSuite) TestParseStaticGitContext_Blob() { + blobUrl := "https://gogs-host.com/daytonaio/daytona/src/main/README.md" + blobContext := &StaticGitContext{ + Id: "daytona", + Name: "daytona", + Owner: "daytonaio", + Url: "https://gogs-host.com/daytonaio/daytona.git", + Source: "gogs-host.com", + Branch: util.Pointer("main"), + Sha: nil, + PrNumber: nil, + Path: util.Pointer("README.md"), + } + + require := g.Require() + + httpContext, err := g.gitProvider.ParseStaticGitContext(blobUrl) + + require.Nil(err) + require.Equal(httpContext, blobContext) +} + +func (g *GogsGitProviderTestSuite) TestParseStaticGitContext_Branch() { + branchUrl := "https://gogs-host.com/daytonaio/daytona/src/main" + branchContext := &StaticGitContext{ + Id: "daytona", + Name: "daytona", + Owner: "daytonaio", + Url: "https://gogs-host.com/daytonaio/daytona.git", + Source: "gogs-host.com", + Branch: util.Pointer("main"), + Sha: nil, + PrNumber: nil, + Path: nil, + } + + require := g.Require() + + httpContext, err := g.gitProvider.ParseStaticGitContext(branchUrl) + + require.Nil(err) + require.Equal(httpContext, branchContext) +} + +func (g *GogsGitProviderTestSuite) TestParseStaticGitContext_Commits() { + commitsUrl := "https://gogs-host.com/daytonaio/daytona/commits/main" + commitsContext := &StaticGitContext{ + Id: "daytona", + Name: "daytona", + Owner: "daytonaio", + Url: "https://gogs-host.com/daytonaio/daytona.git", + Source: "gogs-host.com", + Branch: util.Pointer("main"), + Sha: nil, + PrNumber: nil, + Path: nil, + } + + require := g.Require() + + httpContext, err := g.gitProvider.ParseStaticGitContext(commitsUrl) + + require.Nil(err) + require.Equal(httpContext, commitsContext) +} + +func (g *GogsGitProviderTestSuite) TestParseStaticGitContext_Commit() { + commitUrl := "https://gogs-host.com/daytonaio/daytona/commit/COMMIT_SHA" + commitContext := &StaticGitContext{ + Id: "daytona", + Name: "daytona", + Owner: "daytonaio", + Url: "https://gogs-host.com/daytonaio/daytona.git", + Source: "gogs-host.com", + Branch: util.Pointer("COMMIT_SHA"), + Sha: util.Pointer("COMMIT_SHA"), + PrNumber: nil, + Path: nil, + } + + require := g.Require() + + httpContext, err := g.gitProvider.ParseStaticGitContext(commitUrl) + + require.Nil(err) + require.Equal(httpContext, commitContext) +} + +func (g *GogsGitProviderTestSuite) TestGetUrlFromRepo_Bare() { + repo := &GetRepositoryContext{ + Id: util.Pointer("daytona"), + Name: util.Pointer("daytona"), + Owner: util.Pointer("daytonaio"), + Source: util.Pointer("gogs-host.com"), + Url: "https://gogs-host.com/daytonaio/daytona.git", + } + + require := g.Require() + + url := g.gitProvider.GetUrlFromContext(repo) + + require.Equal("https://gogs-host.com/daytonaio/daytona", url) +} + +func (g *GogsGitProviderTestSuite) TestGetUrlFromRepo_Branch() { + repo := &GetRepositoryContext{ + Id: util.Pointer("daytona"), + Name: util.Pointer("daytona"), + Owner: util.Pointer("daytonaio"), + Source: util.Pointer("gogs-host.com"), + Url: "https://gogs-host.com/daytonaio/daytona.git", + Branch: util.Pointer("test-branch"), + } + + require := g.Require() + + url := g.gitProvider.GetUrlFromContext(repo) + + require.Equal("https://gogs-host.com/daytonaio/daytona/src/test-branch", url) +} + +func (g *GogsGitProviderTestSuite) TestGetUrlFromRepo_Path() { + repo := &GetRepositoryContext{ + Id: util.Pointer("daytona"), + Name: util.Pointer("daytona"), + Owner: util.Pointer("daytonaio"), + Source: util.Pointer("gogs-host.com"), + Url: "https://gogs-host.com/daytonaio/daytona.git", + Branch: util.Pointer("test-branch"), + Path: util.Pointer("README.md"), + } + + require := g.Require() + + url := g.gitProvider.GetUrlFromContext(repo) + + require.Equal("https://gogs-host.com/daytonaio/daytona/src/test-branch/README.md", url) + + repo.Branch = nil + + url = g.gitProvider.GetUrlFromContext(repo) + + require.Equal("https://gogs-host.com/daytonaio/daytona/src/main/README.md", url) +} + +func (g *GogsGitProviderTestSuite) TestGetUrlFromRepo_Commit() { + repo := &GetRepositoryContext{ + Id: util.Pointer("daytona"), + Name: util.Pointer("daytona"), + Owner: util.Pointer("daytonaio"), + Source: util.Pointer("gogs-host.com"), + Url: "https://gogs-host.com/daytonaio/daytona.git", + Sha: util.Pointer("COMMIT_SHA"), + Branch: util.Pointer("COMMIT_SHA"), + Path: util.Pointer("README.md"), + } + + require := g.Require() + + url := g.gitProvider.GetUrlFromContext(repo) + + require.Equal("https://gogs-host.com/daytonaio/daytona/commit/COMMIT_SHA/README.md", url) + + repo.Path = nil + + url = g.gitProvider.GetUrlFromContext(repo) + + require.Equal("https://gogs-host.com/daytonaio/daytona/commit/COMMIT_SHA", url) +} + +func TestGogsGitProvider(t *testing.T) { + suite.Run(t, NewGogsGitProviderTestSuite()) +} diff --git a/pkg/provider/manager/manager.go b/pkg/provider/manager/manager.go index e8c5bd0a5c..caec4a0c89 100644 --- a/pkg/provider/manager/manager.go +++ b/pkg/provider/manager/manager.go @@ -147,13 +147,13 @@ func (m *ProviderManager) RegisterProvider(pluginPath string) error { return errors.New("failed to get targets: " + err.Error()) } - defaultTargets, err := (*p).GetDefaultTargets() + presetTargets, err := (*p).GetPresetTargets() if err != nil { - return errors.New("failed to get default targets: " + err.Error()) + return errors.New("failed to get preset targets: " + err.Error()) } - log.Info("Setting default targets") - for _, target := range *defaultTargets { + log.Info("Setting preset targets") + for _, target := range *presetTargets { if _, ok := existingTargets[target.Name]; ok { log.Infof("Target %s already exists. Skipping...", target.Name) continue @@ -166,7 +166,7 @@ func (m *ProviderManager) RegisterProvider(pluginPath string) error { log.Infof("Target %s set", target.Name) } } - log.Info("Default targets set") + log.Info("Preset targets set") log.Infof("Provider %s initialized", pluginRef.name) diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index f6fca3005f..0e031e2587 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -17,7 +17,7 @@ type Provider interface { GetInfo() (ProviderInfo, error) GetTargetManifest() (*ProviderTargetManifest, error) - GetDefaultTargets() (*[]ProviderTarget, error) + GetPresetTargets() (*[]ProviderTarget, error) CreateWorkspace(*WorkspaceRequest) (*util.Empty, error) StartWorkspace(*WorkspaceRequest) (*util.Empty, error) diff --git a/pkg/provider/rpc_client.go b/pkg/provider/rpc_client.go index 6088c94c55..f433ea052e 100644 --- a/pkg/provider/rpc_client.go +++ b/pkg/provider/rpc_client.go @@ -33,9 +33,9 @@ func (m *ProviderRPCClient) GetTargetManifest() (*ProviderTargetManifest, error) return &resp, err } -func (m *ProviderRPCClient) GetDefaultTargets() (*[]ProviderTarget, error) { +func (m *ProviderRPCClient) GetPresetTargets() (*[]ProviderTarget, error) { var resp []ProviderTarget - err := m.client.Call("Plugin.GetDefaultTargets", new(interface{}), &resp) + err := m.client.Call("Plugin.GetPresetTargets", new(interface{}), &resp) return &resp, err } diff --git a/pkg/provider/rpc_server.go b/pkg/provider/rpc_server.go index 9e24be61c3..8f6bde2b67 100644 --- a/pkg/provider/rpc_server.go +++ b/pkg/provider/rpc_server.go @@ -38,8 +38,8 @@ func (m *ProviderRPCServer) GetTargetManifest(arg interface{}, resp *ProviderTar return nil } -func (m *ProviderRPCServer) GetDefaultTargets(arg interface{}, resp *[]ProviderTarget) error { - targets, err := m.Impl.GetDefaultTargets() +func (m *ProviderRPCServer) GetPresetTargets(arg interface{}, resp *[]ProviderTarget) error { + targets, err := m.Impl.GetPresetTargets() if err != nil { return err } diff --git a/pkg/provider/store.go b/pkg/provider/store.go index 75c79d1d7b..82aa0a2d6b 100644 --- a/pkg/provider/store.go +++ b/pkg/provider/store.go @@ -5,18 +5,20 @@ package provider import "errors" -type Store interface { +type TargetFilter struct { + Name *string + Default *bool } type TargetStore interface { - List() ([]*ProviderTarget, error) - Find(targetName string) (*ProviderTarget, error) + List(filter *TargetFilter) ([]*ProviderTarget, error) + Find(filter *TargetFilter) (*ProviderTarget, error) Save(target *ProviderTarget) error Delete(target *ProviderTarget) error } var ( - ErrTargetNotFound = errors.New("provider not found") + ErrTargetNotFound = errors.New("target not found") ) func IsTargetNotFound(err error) bool { diff --git a/pkg/provider/types.go b/pkg/provider/types.go index 4856af89e1..72abd2d331 100644 --- a/pkg/provider/types.go +++ b/pkg/provider/types.go @@ -47,7 +47,8 @@ type ProviderTarget struct { Name string `json:"name" validate:"required"` ProviderInfo ProviderInfo `json:"providerInfo" validate:"required"` // JSON encoded map of options - Options string `json:"options" validate:"required"` + Options string `json:"options" validate:"required"` + IsDefault bool `json:"isDefault" validate:"required"` } // @name ProviderTarget type ProviderTargetManifest map[string]ProviderTargetProperty // @name ProviderTargetManifest diff --git a/pkg/server/gitproviders/gitprovider.go b/pkg/server/gitproviders/gitprovider.go index 8d2c4ce7ce..6f4356c358 100644 --- a/pkg/server/gitproviders/gitprovider.go +++ b/pkg/server/gitproviders/gitprovider.go @@ -19,6 +19,9 @@ func (s *GitProviderService) GetGitProviderForUrl(repoUrl string) (gitprovider.G return nil, "", err } + var eligibleProvider gitprovider.GitProvider + var eligibleProviderId string + for _, p := range gitProviders { gitProvider, err := s.GetGitProvider(p.Id) if err != nil { @@ -33,9 +36,15 @@ func (s *GitProviderService) GetGitProviderForUrl(repoUrl string) (gitprovider.G if err == nil { return gitProvider, p.Id, nil } + eligibleProvider = gitProvider + eligibleProviderId = p.ProviderId } } + if eligibleProvider != nil { + return eligibleProvider, eligibleProviderId, nil + } + for _, p := range config.GetSupportedGitProviders() { gitProvider, err := s.newGitProvider(&gitprovider.GitProviderConfig{ ProviderId: p.Id, diff --git a/pkg/server/gitproviders/service.go b/pkg/server/gitproviders/service.go index 4b487b90c5..98d038c407 100644 --- a/pkg/server/gitproviders/service.go +++ b/pkg/server/gitproviders/service.go @@ -182,6 +182,8 @@ func (s *GitProviderService) newGitProvider(config *gitprovider.GitProviderConfi return gitprovider.NewAzureDevOpsGitProvider(config.Token, baseApiUrl), nil case "aws-codecommit": return gitprovider.NewAwsCodeCommitGitProvider(baseApiUrl), nil + case "gogs": + return gitprovider.NewGogsGitProvider(config.Token, baseApiUrl), nil default: return nil, errors.New("git provider not found") } diff --git a/pkg/server/projectconfig/prebuild.go b/pkg/server/projectconfig/prebuild.go index acfa1487bd..5c28533bdf 100644 --- a/pkg/server/projectconfig/prebuild.go +++ b/pkg/server/projectconfig/prebuild.go @@ -238,7 +238,6 @@ func (s *ProjectConfigService) ProcessGitEvent(data gitprovider.GitEventData) er if err != nil { return err } - gitProvider, _, err := s.gitProviderService.GetGitProviderForUrl(data.Url) if err != nil { return fmt.Errorf("failed to get git provider for URL: %s", err) diff --git a/pkg/server/providertargets/dto/providertargets.go b/pkg/server/providertargets/dto/providertargets.go new file mode 100644 index 0000000000..fc2e6411cc --- /dev/null +++ b/pkg/server/providertargets/dto/providertargets.go @@ -0,0 +1,12 @@ +// Copyright 2024 Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package dto + +import "github.com/daytonaio/daytona/pkg/provider" + +type CreateProviderTargetDTO struct { + Name string `json:"name" validate:"required"` + ProviderInfo provider.ProviderInfo `json:"providerInfo" validate:"required"` + Options string `json:"options" validate:"required"` +} // @name CreateProviderTargetDTO diff --git a/pkg/server/providertargets/service.go b/pkg/server/providertargets/service.go index 1ca23e4196..4a2f22834c 100644 --- a/pkg/server/providertargets/service.go +++ b/pkg/server/providertargets/service.go @@ -3,14 +3,18 @@ package providertargets -import "github.com/daytonaio/daytona/pkg/provider" +import ( + "github.com/daytonaio/daytona/internal/util" + "github.com/daytonaio/daytona/pkg/provider" +) type IProviderTargetService interface { Delete(target *provider.ProviderTarget) error - Find(targetName string) (*provider.ProviderTarget, error) - List() ([]*provider.ProviderTarget, error) + Find(filter *provider.TargetFilter) (*provider.ProviderTarget, error) + List(filter *provider.TargetFilter) ([]*provider.ProviderTarget, error) Map() (map[string]*provider.ProviderTarget, error) Save(target *provider.ProviderTarget) error + SetDefault(target *provider.ProviderTarget) error } type ProviderTargetServiceConfig struct { @@ -27,12 +31,12 @@ func NewProviderTargetService(config ProviderTargetServiceConfig) IProviderTarge } } -func (s *ProviderTargetService) List() ([]*provider.ProviderTarget, error) { - return s.targetStore.List() +func (s *ProviderTargetService) List(filter *provider.TargetFilter) ([]*provider.ProviderTarget, error) { + return s.targetStore.List(filter) } func (s *ProviderTargetService) Map() (map[string]*provider.ProviderTarget, error) { - list, err := s.targetStore.List() + list, err := s.targetStore.List(nil) if err != nil { return nil, err } @@ -45,14 +49,46 @@ func (s *ProviderTargetService) Map() (map[string]*provider.ProviderTarget, erro return targets, nil } -func (s *ProviderTargetService) Find(targetName string) (*provider.ProviderTarget, error) { - return s.targetStore.Find(targetName) +func (s *ProviderTargetService) Find(filter *provider.TargetFilter) (*provider.ProviderTarget, error) { + return s.targetStore.Find(filter) } func (s *ProviderTargetService) Save(target *provider.ProviderTarget) error { - return s.targetStore.Save(target) + err := s.targetStore.Save(target) + if err != nil { + return err + } + + return s.SetDefault(target) } func (s *ProviderTargetService) Delete(target *provider.ProviderTarget) error { return s.targetStore.Delete(target) } + +func (s *ProviderTargetService) SetDefault(target *provider.ProviderTarget) error { + currentTarget, err := s.Find(&provider.TargetFilter{ + Name: &target.Name, + }) + if err != nil { + return err + } + + defaultTarget, err := s.Find(&provider.TargetFilter{ + Default: util.Pointer(true), + }) + if err != nil && err != provider.ErrTargetNotFound { + return err + } + + if defaultTarget != nil { + defaultTarget.IsDefault = false + err := s.targetStore.Save(defaultTarget) + if err != nil { + return err + } + } + + currentTarget.IsDefault = true + return s.targetStore.Save(currentTarget) +} diff --git a/pkg/server/providertargets/service_test.go b/pkg/server/providertargets/service_test.go index ae21f0aed2..05a14bdbb1 100644 --- a/pkg/server/providertargets/service_test.go +++ b/pkg/server/providertargets/service_test.go @@ -89,7 +89,7 @@ func TestProviderTargetService(t *testing.T) { func (s *ProviderTargetServiceTestSuite) TestList() { require := s.Require() - providerTargets, err := s.providerTargetService.List() + providerTargets, err := s.providerTargetService.List(nil) require.Nil(err) require.ElementsMatch(expectedProviderTargets, providerTargets) } @@ -105,11 +105,27 @@ func (s *ProviderTargetServiceTestSuite) TestMap() { func (s *ProviderTargetServiceTestSuite) TestFind() { require := s.Require() - providerTarget, err := s.providerTargetService.Find(providerTarget1.Name) + providerTarget, err := s.providerTargetService.Find(&provider.TargetFilter{ + Name: &providerTarget1.Name, + }) require.Nil(err) require.Equal(providerTarget1, providerTarget) } +func (s *ProviderTargetServiceTestSuite) TestSetDefault() { + require := s.Require() + + err := s.providerTargetService.SetDefault(providerTarget2) + require.Nil(err) + + providerTarget, err := s.providerTargetService.Find(&provider.TargetFilter{ + Name: &providerTarget2.Name, + }) + require.Nil(err) + + require.Equal(providerTarget2, providerTarget) +} + func (s *ProviderTargetServiceTestSuite) TestSave() { expectedProviderTargets = append(expectedProviderTargets, providerTarget4) @@ -118,7 +134,7 @@ func (s *ProviderTargetServiceTestSuite) TestSave() { err := s.providerTargetService.Save(providerTarget4) require.Nil(err) - providerTargets, err := s.providerTargetService.List() + providerTargets, err := s.providerTargetService.List(nil) require.Nil(err) require.ElementsMatch(expectedProviderTargets, providerTargets) } @@ -131,7 +147,7 @@ func (s *ProviderTargetServiceTestSuite) TestDelete() { err := s.providerTargetService.Delete(providerTarget3) require.Nil(err) - providerTargets, err := s.providerTargetService.List() + providerTargets, err := s.providerTargetService.List(nil) require.Nil(err) require.ElementsMatch(expectedProviderTargets, providerTargets) } diff --git a/pkg/server/workspaces/create.go b/pkg/server/workspaces/create.go index bac065282f..5fac385b65 100644 --- a/pkg/server/workspaces/create.go +++ b/pkg/server/workspaces/create.go @@ -132,7 +132,7 @@ func (s *WorkspaceService) CreateWorkspace(ctx context.Context, req dto.CreateWo return nil, err } - target, err := s.targetStore.Find(w.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &w.Target}) if err != nil { return w, err } diff --git a/pkg/server/workspaces/get.go b/pkg/server/workspaces/get.go index 811f7d765f..3b705e2461 100644 --- a/pkg/server/workspaces/get.go +++ b/pkg/server/workspaces/get.go @@ -9,6 +9,7 @@ import ( "fmt" "time" + "github.com/daytonaio/daytona/pkg/provider" "github.com/daytonaio/daytona/pkg/provisioner" "github.com/daytonaio/daytona/pkg/server/workspaces/dto" log "github.com/sirupsen/logrus" @@ -28,7 +29,7 @@ func (s *WorkspaceService) GetWorkspace(ctx context.Context, workspaceId string, return &response, nil } - target, err := s.targetStore.Find(ws.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &ws.Target}) if err != nil { return nil, err } diff --git a/pkg/server/workspaces/list.go b/pkg/server/workspaces/list.go index f06e312746..996bb6bc39 100644 --- a/pkg/server/workspaces/list.go +++ b/pkg/server/workspaces/list.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/daytonaio/daytona/pkg/provider" "github.com/daytonaio/daytona/pkg/provisioner" "github.com/daytonaio/daytona/pkg/server/workspaces/dto" log "github.com/sirupsen/logrus" @@ -34,7 +35,7 @@ func (s *WorkspaceService) ListWorkspaces(ctx context.Context, verbose bool) ([] go func(i int) { defer wg.Done() - target, err := s.targetStore.Find(w.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &w.Target}) if err != nil { log.Error(fmt.Errorf("failed to get target for %s", w.Target)) return diff --git a/pkg/server/workspaces/remove.go b/pkg/server/workspaces/remove.go index 5b002d481d..bda3117d2b 100644 --- a/pkg/server/workspaces/remove.go +++ b/pkg/server/workspaces/remove.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/daytonaio/daytona/pkg/logs" + "github.com/daytonaio/daytona/pkg/provider" "github.com/daytonaio/daytona/pkg/telemetry" log "github.com/sirupsen/logrus" ) @@ -20,7 +21,7 @@ func (s *WorkspaceService) RemoveWorkspace(ctx context.Context, workspaceId stri log.Infof("Destroying workspace %s", workspace.Id) - target, err := s.targetStore.Find(workspace.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &workspace.Target}) if err != nil { return err } @@ -96,7 +97,7 @@ func (s *WorkspaceService) ForceRemoveWorkspace(ctx context.Context, workspaceId log.Infof("Destroying workspace %s", workspace.Id) - target, _ := s.targetStore.Find(workspace.Target) + target, _ := s.targetStore.Find(&provider.TargetFilter{Name: &workspace.Target}) for _, project := range workspace.Projects { // todo: go routines diff --git a/pkg/server/workspaces/service.go b/pkg/server/workspaces/service.go index 7236d9c62d..e28f544546 100644 --- a/pkg/server/workspaces/service.go +++ b/pkg/server/workspaces/service.go @@ -38,7 +38,7 @@ type IWorkspaceService interface { } type targetStore interface { - Find(targetName string) (*provider.ProviderTarget, error) + Find(filter *provider.TargetFilter) (*provider.ProviderTarget, error) } type WorkspaceServiceConfig struct { diff --git a/pkg/server/workspaces/start.go b/pkg/server/workspaces/start.go index fb4dd1f444..565a9c41f0 100644 --- a/pkg/server/workspaces/start.go +++ b/pkg/server/workspaces/start.go @@ -24,7 +24,7 @@ func (s *WorkspaceService) StartWorkspace(ctx context.Context, workspaceId strin return ErrWorkspaceNotFound } - target, err := s.targetStore.Find(w.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &w.Target}) if err != nil { return err } @@ -67,7 +67,7 @@ func (s *WorkspaceService) StartProject(ctx context.Context, workspaceId, projec return ErrProjectNotFound } - target, err := s.targetStore.Find(project.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &w.Target}) if err != nil { return err } diff --git a/pkg/server/workspaces/stop.go b/pkg/server/workspaces/stop.go index e11ecaf160..83344bb5d7 100644 --- a/pkg/server/workspaces/stop.go +++ b/pkg/server/workspaces/stop.go @@ -7,6 +7,7 @@ import ( "context" "time" + "github.com/daytonaio/daytona/pkg/provider" "github.com/daytonaio/daytona/pkg/telemetry" log "github.com/sirupsen/logrus" ) @@ -17,7 +18,7 @@ func (s *WorkspaceService) StopWorkspace(ctx context.Context, workspaceId string return ErrWorkspaceNotFound } - target, err := s.targetStore.Find(workspace.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &workspace.Target}) if err != nil { return err } @@ -70,7 +71,7 @@ func (s *WorkspaceService) StopProject(ctx context.Context, workspaceId, project return ErrProjectNotFound } - target, err := s.targetStore.Find(w.Target) + target, err := s.targetStore.Find(&provider.TargetFilter{Name: &w.Target}) if err != nil { return err } diff --git a/pkg/views/gitprovider/select.go b/pkg/views/gitprovider/select.go index 0d67086864..b14c13fd0f 100644 --- a/pkg/views/gitprovider/select.go +++ b/pkg/views/gitprovider/select.go @@ -200,7 +200,17 @@ func providerRequiresUsername(gitProviderId string) bool { } func providerRequiresApiUrl(gitProviderId string) bool { - return gitProviderId == "gitness" || gitProviderId == "github-enterprise-server" || gitProviderId == "gitlab-self-managed" || gitProviderId == "gitea" || gitProviderId == "bitbucket-server" || gitProviderId == "azure-devops" || gitProviderId == "aws-codecommit" + providersRequiringApiUrl := []string{ + "gitness", + "github-enterprise-server", + "gitlab-self-managed", + "gitea", + "bitbucket-server", + "azure-devops", + "aws-codecommit", + "gogs", + } + return slices.Contains(providersRequiringApiUrl, gitProviderId) } func commitSigningNotSupported(gitProviderId string) bool { @@ -222,6 +232,8 @@ func getApiUrlDescription(gitProviderId string) string { return "For example: https://bitbucket.host.com/rest" } else if gitProviderId == "aws-codecommit" { return "For example: https://ap-south-1.console.aws.amazon.com" + } else if gitProviderId == "gogs" { + return "For example: https://gogs-host.com" } return "" } @@ -238,12 +250,17 @@ func getSigningKeyDescription(signingMethod string) string { } func getGitProviderHelpMessage(gitProviderId string) string { - message := fmt.Sprintf("%s\n%s\n\n%s%s", + message := fmt.Sprintf("%s\n%s", lipgloss.NewStyle().Foreground(views.Green).Bold(true).Render("More information on:"), - config.GetDocsLinkFromGitProvider(gitProviderId), - lipgloss.NewStyle().Foreground(views.Green).Bold(true).Render("Required scopes: "), - config.GetRequiredScopesFromGitProviderId(gitProviderId)) + config.GetDocsLinkFromGitProvider(gitProviderId)) + requiredScopes := config.GetRequiredScopesFromGitProviderId(gitProviderId) + if requiredScopes != "" { + message = fmt.Sprintf("%s\n\n%s%s", + message, + lipgloss.NewStyle().Foreground(views.Green).Bold(true).Render("Required scopes: "), + requiredScopes) + } prebuildScopes := config.GetPrebuildScopesFromGitProviderId(gitProviderId) if prebuildScopes != "" { message = fmt.Sprintf("%s\n%s%s", diff --git a/pkg/views/projectconfig/info/view.go b/pkg/views/projectconfig/info/view.go index 59149ab063..84ffd20f37 100644 --- a/pkg/views/projectconfig/info/view.go +++ b/pkg/views/projectconfig/info/view.go @@ -37,6 +37,13 @@ func Render(projectConfig *apiclient.ProjectConfig, apiServerConfig *apiclient.S output += getInfoLine("Repository", projectConfig.RepositoryUrl) + "\n" + gitProviderConfig := "/" + if projectConfig.GitProviderConfigId != nil { + gitProviderConfig = *projectConfig.GitProviderConfigId + } + + output += getInfoLine("Git Provider ID", gitProviderConfig) + "\n" + if projectConfig.Default { output += getInfoLine("Default", "Yes") + "\n" } diff --git a/pkg/views/projectconfig/list/view.go b/pkg/views/projectconfig/list/view.go index 012cf5d271..5ca809a929 100644 --- a/pkg/views/projectconfig/list/view.go +++ b/pkg/views/projectconfig/list/view.go @@ -54,7 +54,6 @@ func getRowFromData(projectConfig apiclient.ProjectConfig, apiServerConfig *apic data.Name = projectConfig.Name + views_util.AdditionalPropertyPadding data.Repository = util.GetRepositorySlugFromUrl(projectConfig.RepositoryUrl, specifyGitProviders) data.Prebuilds = "None" - data.IsDefault = "" projectDefaults := &views_util.ProjectConfigDefaults{ Image: &apiServerConfig.DefaultProjectImage, @@ -68,19 +67,15 @@ func getRowFromData(projectConfig apiclient.ProjectConfig, apiServerConfig *apic _, data.Build = views_util.GetProjectBuildChoice(createProjectDto, projectDefaults) if projectConfig.Default { - data.IsDefault = "1" + isDefault = views.ActiveStyle.Render("Yes") + } else { + isDefault = views.InactiveStyle.Render("/") } if len(projectConfig.Prebuilds) > 0 { data.Prebuilds = fmt.Sprintf("%d", len(projectConfig.Prebuilds)) } - if data.IsDefault == "" { - isDefault = views.InactiveStyle.Render("/") - } else { - isDefault = views.ActiveStyle.Render("Yes") - } - return []string{ views.NameStyle.Render(data.Name), views.DefaultRowDataStyle.Render(data.Repository), diff --git a/pkg/views/target/list/view.go b/pkg/views/target/list/view.go index 7a9829cdfa..14bf3ac03d 100644 --- a/pkg/views/target/list/view.go +++ b/pkg/views/target/list/view.go @@ -13,9 +13,10 @@ import ( ) type rowData struct { - Target string - Provider string - Options string + Target string + Provider string + IsDefault string + Options string } func ListTargets(targetList []apiclient.ProviderTarget) { @@ -28,7 +29,7 @@ func ListTargets(targetList []apiclient.ProviderTarget) { } table := util.GetTableView(data, []string{ - "Target", "Provider", "Options", + "Target", "Provider", "Default", "Options", }, nil, func() { renderUnstyledList(targetList) }) @@ -37,15 +38,23 @@ func ListTargets(targetList []apiclient.ProviderTarget) { } func getRowFromRowData(target *apiclient.ProviderTarget) []string { + var isDefault string var data rowData data.Target = target.Name data.Provider = target.ProviderInfo.Name data.Options = target.Options + if target.IsDefault { + isDefault = views.ActiveStyle.Render("Yes") + } else { + isDefault = views.InactiveStyle.Render("/") + } + row := []string{ views.NameStyle.Render(data.Target), views.DefaultRowDataStyle.Render(data.Provider), + isDefault, views.DefaultRowDataStyle.Render(data.Options), } @@ -68,7 +77,11 @@ func renderUnstyledList(targetList []apiclient.ProviderTarget) { output += fmt.Sprintf("%s %s", views.GetPropertyKey("Target Provider: "), target.ProviderInfo.Name) + "\n\n" - output += fmt.Sprintf("%s %s", views.GetPropertyKey("Target Options: "), target.Options) + "\n" + if target.IsDefault { + output += fmt.Sprintf("%s %s", views.GetPropertyKey("Default: "), "Yes") + "\n\n" + } + + output += fmt.Sprintf("%s %s", views.GetPropertyKey("Target Options: "), target.Options) + "\n\n" if target.Name != targetList[len(targetList)-1].Name { output += views.SeparatorString + "\n\n" diff --git a/pkg/views/target/select.go b/pkg/views/target/select.go index 20c1ce3c0c..d3f9a345b3 100644 --- a/pkg/views/target/select.go +++ b/pkg/views/target/select.go @@ -20,6 +20,7 @@ const NewTargetName = "+ New Target" type TargetView struct { Name string Options string + IsDefault bool ProviderInfo ProviderInfo } @@ -29,7 +30,7 @@ type ProviderInfo struct { Installed *bool } -func GetTargetFromPrompt(targets []apiclient.ProviderTarget, activeProfileName string, providerViewList *[]provider.ProviderView, withNewTarget bool) (*TargetView, error) { +func GetTargetFromPrompt(targets []apiclient.ProviderTarget, activeProfileName string, providerViewList *[]provider.ProviderView, withNewTarget bool, actionVerb string) (*TargetView, error) { items := util.ArrayMap(targets, func(t apiclient.ProviderTarget) list.Item { return item{ target: GetTargetViewFromTarget(t), @@ -76,7 +77,7 @@ func GetTargetFromPrompt(targets []apiclient.ProviderTarget, activeProfileName s l := views.GetStyledSelectList(items) m := model{list: l} - m.list.Title = views.GetStyledMainTitle("Choose a Target") + m.list.Title = views.GetStyledMainTitle("Choose a Target To " + actionVerb) m.footer = views.GetListFooter(activeProfileName, views.DefaultListFooterPadding) p, err := tea.NewProgram(m, tea.WithAltScreen()).Run() @@ -93,8 +94,9 @@ func GetTargetFromPrompt(targets []apiclient.ProviderTarget, activeProfileName s func GetTargetViewFromTarget(target apiclient.ProviderTarget) TargetView { return TargetView{ - Name: target.Name, - Options: target.Options, + Name: target.Name, + Options: target.Options, + IsDefault: target.IsDefault, ProviderInfo: ProviderInfo{ Name: target.ProviderInfo.Name, Version: target.ProviderInfo.Version, diff --git a/pkg/views/target/view.go b/pkg/views/target/view.go index d229b45caf..d63112ef3b 100644 --- a/pkg/views/target/view.go +++ b/pkg/views/target/view.go @@ -16,7 +16,15 @@ type item struct { target TargetView } -func (i item) Title() string { return i.target.Name } +func (i item) Title() string { + title := i.target.Name + + if i.target.IsDefault { + title += " (default)" + } + + return title +} func (i item) Description() string { desc := i.target.ProviderInfo.Name if i.target.ProviderInfo.Installed != nil {