diff --git a/app/api/controller/aiagent/analyse_execution.go b/app/api/controller/aiagent/analyse_execution.go new file mode 100644 index 0000000000..f0accdaf95 --- /dev/null +++ b/app/api/controller/aiagent/analyse_execution.go @@ -0,0 +1,59 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/app/api/auth" + "github.com/harness/gitness/app/api/usererror" + "github.com/harness/gitness/app/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) GetAnalysis( + ctx context.Context, + session *auth.Session, + repoRef string, + pipelineIdentifier string, + executionNum int64, +) (*types.AnalyseExecutionOutput, error) { + repo, err := c.repoStore.FindByRef(ctx, repoRef) + if err != nil { + return nil, usererror.BadRequestf("failed to find repo %s", repoRef) + } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView) + if err != nil { + return nil, usererror.Forbidden(fmt.Sprintf("not allowed to view pipeline %s", pipelineIdentifier)) + } + + pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier) + if err != nil { + return nil, usererror.BadRequestf("failed to find pipeline: %s", pipelineIdentifier) + } + + execution, err := c.executionStore.FindByNumber(ctx, pipeline.ID, executionNum) + if err != nil { + return nil, usererror.BadRequestf("failed to find execution %d", executionNum) + } + + if execution.Status == enum.CIStatusSuccess { + return nil, usererror.BadRequestf("execution %d is not a failed execution", executionNum) + } + + return &types.AnalyseExecutionOutput{Yaml: "a:1"}, nil +} diff --git a/app/api/controller/aiagent/controller.go b/app/api/controller/aiagent/controller.go new file mode 100644 index 0000000000..0af06a124c --- /dev/null +++ b/app/api/controller/aiagent/controller.go @@ -0,0 +1,45 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "github.com/harness/gitness/app/auth/authz" + "github.com/harness/gitness/app/services/aiagent" + "github.com/harness/gitness/app/store" +) + +type Controller struct { + authorizer authz.Authorizer + pipeline *aiagent.HarnessIntelligence + repoStore store.RepoStore + pipelineStore store.PipelineStore + executionStore store.ExecutionStore +} + +func NewController( + authorizer authz.Authorizer, + pipeline *aiagent.HarnessIntelligence, + repoStore store.RepoStore, + pipelineStore store.PipelineStore, + executionStore store.ExecutionStore, +) *Controller { + return &Controller{ + authorizer: authorizer, + pipeline: pipeline, + repoStore: repoStore, + pipelineStore: pipelineStore, + executionStore: executionStore, + } +} diff --git a/app/api/controller/aiagent/generate_pipeline.go b/app/api/controller/aiagent/generate_pipeline.go new file mode 100644 index 0000000000..5a9a124f04 --- /dev/null +++ b/app/api/controller/aiagent/generate_pipeline.go @@ -0,0 +1,48 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "context" + "fmt" + + "github.com/harness/gitness/types" +) + +type GeneratePipelineInput struct { + Prompt string `json:"prompt"` + RepoRef string `json:"repo_ref"` +} + +type GeneratePipelineOutput struct { + Yaml string `json:"yaml"` +} + +func (c *Controller) GeneratePipeline( + ctx context.Context, + in *GeneratePipelineInput, +) (*GeneratePipelineOutput, error) { + generateRequest := &types.PipelineGenerateRequest{ + Prompt: in.Prompt, + RepoRef: in.RepoRef, + } + output, err := c.pipeline.Generate(ctx, generateRequest) + if err != nil { + return nil, fmt.Errorf("generate pipeline: %w", err) + } + return &GeneratePipelineOutput{ + Yaml: output.YAML, + }, nil +} diff --git a/app/api/controller/aiagent/suggest_pipelines.go b/app/api/controller/aiagent/suggest_pipelines.go new file mode 100644 index 0000000000..9598fcd53b --- /dev/null +++ b/app/api/controller/aiagent/suggest_pipelines.go @@ -0,0 +1,42 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "context" + "fmt" + + "github.com/harness/gitness/types" +) + +type SuggestPipelineInput struct { + RepoRef string `json:"repo_ref"` + Pipeline string `json:"pipeline"` +} + +func (c *Controller) SuggestPipeline( + ctx context.Context, + in *SuggestPipelineInput, +) (*types.PipelineSuggestionsResponse, error) { + suggestionRequest := &types.PipelineSuggestionsRequest{ + RepoRef: in.RepoRef, + Pipeline: in.Pipeline, + } + output, err := c.pipeline.Suggest(ctx, suggestionRequest) + if err != nil { + return nil, fmt.Errorf("suggest pipeline: %w", err) + } + return output, nil +} diff --git a/app/api/controller/aiagent/update_pipeline.go b/app/api/controller/aiagent/update_pipeline.go new file mode 100644 index 0000000000..ae986a4afc --- /dev/null +++ b/app/api/controller/aiagent/update_pipeline.go @@ -0,0 +1,44 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "context" + "fmt" + + "github.com/harness/gitness/types" +) + +type UpdatePipelineInput struct { + Prompt string `json:"prompt"` + RepoRef string `json:"repo_ref"` + Pipeline string `json:"pipeline"` +} + +func (c *Controller) UpdatePipeline( + ctx context.Context, + in *UpdatePipelineInput, +) (string, error) { + generateRequest := &types.PipelineUpdateRequest{ + Prompt: in.Prompt, + RepoRef: in.RepoRef, + Pipeline: in.Pipeline, + } + output, err := c.pipeline.Update(ctx, generateRequest) + if err != nil { + return "", fmt.Errorf("update pipeline: %w", err) + } + return output.YAML, nil +} diff --git a/app/api/controller/aiagent/wire.go b/app/api/controller/aiagent/wire.go new file mode 100644 index 0000000000..dafa74fcd2 --- /dev/null +++ b/app/api/controller/aiagent/wire.go @@ -0,0 +1,44 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "github.com/harness/gitness/app/auth/authz" + "github.com/harness/gitness/app/services/aiagent" + "github.com/harness/gitness/app/store" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideController, +) + +func ProvideController( + authorizer authz.Authorizer, + aiagentPipeline *aiagent.HarnessIntelligence, + repoStore store.RepoStore, + pipelineStore store.PipelineStore, + executionStore store.ExecutionStore, +) *Controller { + return NewController( + authorizer, + aiagentPipeline, + repoStore, + pipelineStore, + executionStore, + ) +} diff --git a/app/api/controller/capabilities/controller.go b/app/api/controller/capabilities/controller.go new file mode 100644 index 0000000000..8974459b0c --- /dev/null +++ b/app/api/controller/capabilities/controller.go @@ -0,0 +1,31 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "github.com/harness/gitness/app/services/capabilities" +) + +type Controller struct { + Capabilities *capabilities.Registry +} + +func NewController( + capabilities *capabilities.Registry, +) *Controller { + return &Controller{ + Capabilities: capabilities, + } +} diff --git a/app/api/controller/capabilities/run_capabilities.go b/app/api/controller/capabilities/run_capabilities.go new file mode 100644 index 0000000000..3f60da97d5 --- /dev/null +++ b/app/api/controller/capabilities/run_capabilities.go @@ -0,0 +1,77 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "context" + "fmt" + + "github.com/harness/gitness/types/capabilities" +) + +type ContextID string + +type RunCapabilitiesRequest struct { + ConversationRaw string `json:"conversation_raw"` + ConversationID ContextID `json:"conversation_id"` + CapabilitiesToRun []CapabilityRunRequest `json:"capabilities_to_run"` +} + +type CapabilityRunRequest struct { + CallID string `json:"call_id"` + Type capabilities.Type `json:"type"` + Input capabilities.Input `json:"input"` +} + +type CapabilityExecution struct { + Type capabilities.Type `json:"capability_id"` + Result capabilities.Output `json:"result"` + ReturnToUser bool `json:"return_to_user"` +} + +func (c CapabilityExecution) GetType() capabilities.AIContextPayloadType { + return "other" +} + +type CapabilityRunResponse struct { + CapabilitiesRan []CapabilityExecution `json:"capabilities_ran"` +} + +func (c *Controller) RunCapabilities(ctx context.Context, req *RunCapabilitiesRequest) (*CapabilityRunResponse, error) { + capOut := new(CapabilityRunResponse) + capOut.CapabilitiesRan = []CapabilityExecution{} + + for _, value := range req.CapabilitiesToRun { + if !c.Capabilities.Exists(value.Type) { + return nil, fmt.Errorf("capability %s does not exist", value.Type) + } + + resp, err := c.Capabilities.Execute(ctx, value.Type, value.Input) + if err != nil { + return nil, err + } + + returnToUser, err := c.Capabilities.ReturnToUser(value.Type) + if err != nil { + return nil, err + } + capOut.CapabilitiesRan = append(capOut.CapabilitiesRan, CapabilityExecution{ + Type: value.Type, + Result: resp, + ReturnToUser: returnToUser, + }) + } + return capOut, nil +} diff --git a/app/api/controller/capabilities/wire.go b/app/api/controller/capabilities/wire.go new file mode 100644 index 0000000000..c8bafa9824 --- /dev/null +++ b/app/api/controller/capabilities/wire.go @@ -0,0 +1,35 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "github.com/harness/gitness/app/services/capabilities" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideController, +) + +func ProvideController( + capabilities *capabilities.Registry, + +) *Controller { + return NewController( + capabilities, + ) +} diff --git a/app/api/controller/execution/analyse.go b/app/api/controller/execution/analyse.go new file mode 100644 index 0000000000..78e3da7654 --- /dev/null +++ b/app/api/controller/execution/analyse.go @@ -0,0 +1,59 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package execution + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/app/api/auth" + "github.com/harness/gitness/app/api/usererror" + "github.com/harness/gitness/app/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +func (c *Controller) GetAnalysis( + ctx context.Context, + session *auth.Session, + repoRef string, + pipelineIdentifier string, + executionNum int64, +) (*types.AnalyseExecutionOutput, error) { + repo, err := c.repoStore.FindByRef(ctx, repoRef) + if err != nil { + return nil, usererror.BadRequestf("failed to find repo %s", repoRef) + } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView) + if err != nil { + return nil, usererror.Forbidden(fmt.Sprintf("not allowed to view pipeline %s", pipelineIdentifier)) + } + + pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier) + if err != nil { + return nil, usererror.BadRequestf("failed to find pipeline: %s", pipelineIdentifier) + } + + execution, err := c.executionStore.FindByNumber(ctx, pipeline.ID, executionNum) + if err != nil { + return nil, usererror.BadRequestf("failed to find execution %d", executionNum) + } + + if execution.Status == enum.CIStatusSuccess { + return nil, usererror.BadRequestf("execution %d is not a failed execution", executionNum) + } + + return &types.AnalyseExecutionOutput{Yaml: "a:1"}, nil +} diff --git a/app/api/controller/pipeline/controller.go b/app/api/controller/pipeline/controller.go index 04c3589efd..6793a757c6 100644 --- a/app/api/controller/pipeline/controller.go +++ b/app/api/controller/pipeline/controller.go @@ -16,6 +16,7 @@ package pipeline import ( "github.com/harness/gitness/app/auth/authz" + events "github.com/harness/gitness/app/events/pipeline" "github.com/harness/gitness/app/store" ) @@ -25,6 +26,7 @@ type Controller struct { triggerStore store.TriggerStore authorizer authz.Authorizer pipelineStore store.PipelineStore + reporter events.Reporter } func NewController( @@ -32,11 +34,13 @@ func NewController( repoStore store.RepoStore, triggerStore store.TriggerStore, pipelineStore store.PipelineStore, + reporter events.Reporter, ) *Controller { return &Controller{ repoStore: repoStore, triggerStore: triggerStore, authorizer: authorizer, pipelineStore: pipelineStore, + reporter: reporter, } } diff --git a/app/api/controller/pipeline/create.go b/app/api/controller/pipeline/create.go index 250c5b248b..ab1b259ffe 100644 --- a/app/api/controller/pipeline/create.go +++ b/app/api/controller/pipeline/create.go @@ -23,6 +23,7 @@ import ( apiauth "github.com/harness/gitness/app/api/auth" "github.com/harness/gitness/app/api/usererror" "github.com/harness/gitness/app/auth" + events "github.com/harness/gitness/app/events/pipeline" "github.com/harness/gitness/types" "github.com/harness/gitness/types/check" "github.com/harness/gitness/types/enum" @@ -107,6 +108,9 @@ func (c *Controller) Create( log.Ctx(ctx).Err(err).Msg("failed to create auto trigger on pipeline creation") } + // send pipeline create event + c.reporter.Created(ctx, &events.CreatedPayload{PipelineID: pipeline.ID, RepoID: pipeline.RepoID}) + return pipeline, nil } diff --git a/app/api/controller/pipeline/update.go b/app/api/controller/pipeline/update.go index 7f046fad4d..6994f86011 100644 --- a/app/api/controller/pipeline/update.go +++ b/app/api/controller/pipeline/update.go @@ -21,6 +21,7 @@ import ( apiauth "github.com/harness/gitness/app/api/auth" "github.com/harness/gitness/app/auth" + events "github.com/harness/gitness/app/events/pipeline" "github.com/harness/gitness/types" "github.com/harness/gitness/types/check" "github.com/harness/gitness/types/enum" @@ -60,7 +61,7 @@ func (c *Controller) Update( return nil, fmt.Errorf("failed to find pipeline: %w", err) } - return c.pipelineStore.UpdateOptLock(ctx, pipeline, func(pipeline *types.Pipeline) error { + updated, err := c.pipelineStore.UpdateOptLock(ctx, pipeline, func(pipeline *types.Pipeline) error { if in.Identifier != nil { pipeline.Identifier = *in.Identifier } @@ -76,6 +77,11 @@ func (c *Controller) Update( return nil }) + + // send pipeline update event + c.reporter.Updated(ctx, &events.UpdatedPayload{PipelineID: pipeline.ID, RepoID: pipeline.RepoID}) + + return updated, err } func (c *Controller) sanitizeUpdateInput(in *UpdateInput) error { diff --git a/app/api/controller/pipeline/wire.go b/app/api/controller/pipeline/wire.go index 44909b6bca..67bb49599f 100644 --- a/app/api/controller/pipeline/wire.go +++ b/app/api/controller/pipeline/wire.go @@ -16,6 +16,7 @@ package pipeline import ( "github.com/harness/gitness/app/auth/authz" + events "github.com/harness/gitness/app/events/pipeline" "github.com/harness/gitness/app/store" "github.com/google/wire" @@ -31,11 +32,13 @@ func ProvideController( triggerStore store.TriggerStore, authorizer authz.Authorizer, pipelineStore store.PipelineStore, + reporter *events.Reporter, ) *Controller { return NewController( authorizer, repoStore, triggerStore, pipelineStore, + *reporter, ) } diff --git a/app/api/controller/space/list_pipelines.go b/app/api/controller/space/list_pipelines.go new file mode 100644 index 0000000000..a5f7703663 --- /dev/null +++ b/app/api/controller/space/list_pipelines.go @@ -0,0 +1,64 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package space + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/app/api/auth" + "github.com/harness/gitness/app/auth" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// ListPipelines lists the pipelines in a space. +func (c *Controller) ListPipelines( + ctx context.Context, + session *auth.Session, + spaceRef string, + filter types.ListQueryFilter, +) ([]*types.Pipeline, int64, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, fmt.Errorf("failed to find space: %w", err) + } + if err := apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionPipelineView); err != nil { + return nil, 0, fmt.Errorf("access check failed: %w", err) + } + + var count int64 + var pipelines []*types.Pipeline + + err = c.tx.WithTx(ctx, func(ctx context.Context) (err error) { + count, err = c.pipelineStore.CountInSpace(ctx, space.ID, filter) + if err != nil { + return fmt.Errorf("failed to count pipelines in space: %w", err) + } + + pipelines, err = c.pipelineStore.ListInSpace(ctx, space.ID, filter) + if err != nil { + return fmt.Errorf("failed to list pipelines in space: %w", err) + } + + return + }, dbtx.TxDefaultReadOnly) + if err != nil { + return pipelines, count, fmt.Errorf("failed to list pipelines in space: %w", err) + } + + return pipelines, count, nil +} diff --git a/app/api/handler/aiagent/analyse_execution.go b/app/api/handler/aiagent/analyse_execution.go new file mode 100644 index 0000000000..8ddc959c86 --- /dev/null +++ b/app/api/handler/aiagent/analyse_execution.go @@ -0,0 +1,65 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/app/api/controller/aiagent" + "github.com/harness/gitness/app/api/render" + "github.com/harness/gitness/app/api/request" + "github.com/harness/gitness/app/api/usererror" + "github.com/harness/gitness/types" +) + +func HandleAnalyse(aiagentCtrl *aiagent.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(types.AnalyseExecutionInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err) + return + } + + pipelineIdentifier := in.PipelineIdentifier + if pipelineIdentifier == "" { + render.TranslatedUserError(ctx, w, usererror.BadRequest("pipeline_identifier is required")) + return + } + repoRef := in.RepoRef + if repoRef == "" { + render.TranslatedUserError(ctx, w, usererror.BadRequest("repo_ref is required")) + return + } + + executionNumber := in.ExecutionNum + if executionNumber < 1 { + render.TranslatedUserError(ctx, w, usererror.BadRequest("execution_number must be greater than 0")) + return + } + + // fetch stored analysis for the given execution, if any + analysis, err := aiagentCtrl.GetAnalysis(ctx, session, repoRef, pipelineIdentifier, in.ExecutionNum) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + render.JSON(w, http.StatusCreated, analysis) + } +} diff --git a/app/api/handler/aiagent/generate_pipeline.go b/app/api/handler/aiagent/generate_pipeline.go new file mode 100644 index 0000000000..f2d180f1ac --- /dev/null +++ b/app/api/handler/aiagent/generate_pipeline.go @@ -0,0 +1,46 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/app/api/controller/aiagent" + "github.com/harness/gitness/app/api/render" +) + +func HandleGeneratePipeline(aiagentCtrl *aiagent.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + in := new(aiagent.GeneratePipelineInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err) + return + } + + pipeline, err := aiagentCtrl.GeneratePipeline(ctx, in) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + w.Header().Set("Content-Type", "text/yaml; charset=utf-8") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(pipeline.Yaml)) + } +} diff --git a/app/api/handler/aiagent/suggest_pipelines.go b/app/api/handler/aiagent/suggest_pipelines.go new file mode 100644 index 0000000000..6d9457efdf --- /dev/null +++ b/app/api/handler/aiagent/suggest_pipelines.go @@ -0,0 +1,44 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/app/api/controller/aiagent" + "github.com/harness/gitness/app/api/render" +) + +func HandleSuggestPipelines(aiagentCtrl *aiagent.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + in := new(aiagent.SuggestPipelineInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err) + return + } + + suggestions, err := aiagentCtrl.SuggestPipeline(ctx, in) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + render.JSON(w, http.StatusOK, suggestions) + } +} diff --git a/app/api/handler/aiagent/update_pipeline.go b/app/api/handler/aiagent/update_pipeline.go new file mode 100644 index 0000000000..af91f01de2 --- /dev/null +++ b/app/api/handler/aiagent/update_pipeline.go @@ -0,0 +1,47 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/app/api/controller/aiagent" + "github.com/harness/gitness/app/api/render" +) + +func HandleUpdatePipeline(aiagentCtrl *aiagent.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // TODO Question: how come we decode body here vs putting that logic in the request package? + in := new(aiagent.UpdatePipelineInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err) + return + } + + pipeline, err := aiagentCtrl.UpdatePipeline(ctx, in) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + w.Header().Set("Content-Type", "text/yaml; charset=utf-8") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(pipeline)) + } +} diff --git a/app/api/handler/capabilities/run_capabilities.go b/app/api/handler/capabilities/run_capabilities.go new file mode 100644 index 0000000000..bb6c2c240c --- /dev/null +++ b/app/api/handler/capabilities/run_capabilities.go @@ -0,0 +1,89 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/harness/gitness/app/api/controller/capabilities" + "github.com/harness/gitness/app/api/render" + capabilitiesservice "github.com/harness/gitness/app/services/capabilities" + capabilities2 "github.com/harness/gitness/types/capabilities" +) + +func HandleRunCapabilities(capabilitiesCtrl *capabilities.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + data, err := io.ReadAll(r.Body) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + in, err := UnmarshalRunCapabilitiesRequest(capabilitiesCtrl.Capabilities, data) + if err != nil { + render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err) + return + } + + resp, err := capabilitiesCtrl.RunCapabilities(ctx, in) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + render.JSON(w, http.StatusOK, resp) + } +} + +// UnmarshalRunCapabilitiesRequest We need this method in order to unmarshall +// the request. We cannot do this in a generic way, because we use the +// capability type in order to deserialize it appropriately (see DeserializeInput, +// as used in this function). +func UnmarshalRunCapabilitiesRequest(cr *capabilitiesservice.Registry, + data []byte) (*capabilities.RunCapabilitiesRequest, error) { + r := &capabilities.RunCapabilitiesRequest{} + type rawCapability struct { + CallID string `json:"call_id"` + Type capabilities2.Type `json:"type"` + Input json.RawMessage + } + var rawBase struct { + ConversationRaw string `json:"conversation_raw"` + ConversationID capabilities.ContextID `json:"conversation_id"` + CapabilitiesToRun []rawCapability `json:"capabilities_to_run"` + } + err := json.Unmarshal(data, &rawBase) + if err != nil { + return nil, err + } + r.CapabilitiesToRun = make([]capabilities.CapabilityRunRequest, 0) + + r.ConversationRaw = rawBase.ConversationRaw + r.ConversationID = rawBase.ConversationID + for _, raw := range rawBase.CapabilitiesToRun { + capabilityRequest := new(capabilities.CapabilityRunRequest) + capabilityRequest.CallID = raw.CallID + capabilityRequest.Type = raw.Type + capabilityRequest.Input, err = capabilitiesservice.DeserializeInput(cr, raw.Type, raw.Input) + if err != nil { + return nil, err + } + r.CapabilitiesToRun = append(r.CapabilitiesToRun, *capabilityRequest) + } + return r, nil +} diff --git a/app/api/handler/space/list_pipelines.go b/app/api/handler/space/list_pipelines.go new file mode 100644 index 0000000000..2c39eeb045 --- /dev/null +++ b/app/api/handler/space/list_pipelines.go @@ -0,0 +1,44 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package space + +import ( + "net/http" + + "github.com/harness/gitness/app/api/controller/space" + "github.com/harness/gitness/app/api/render" + "github.com/harness/gitness/app/api/request" +) + +func HandleListPipelines(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + filter := request.ParseListQueryFilterFromRequest(r) + repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, filter) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.JSON(w, http.StatusOK, repos) + } +} diff --git a/app/events/pipeline/create.go b/app/events/pipeline/create.go new file mode 100644 index 0000000000..81f71f77d8 --- /dev/null +++ b/app/events/pipeline/create.go @@ -0,0 +1,45 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import ( + "context" + + "github.com/harness/gitness/events" + + "github.com/rs/zerolog/log" +) + +const CreatedEvent events.EventType = "created" + +type CreatedPayload struct { + PipelineID int64 `json:"pipeline_id"` + RepoID int64 `json:"repo_id"` +} + +func (r *Reporter) Created(ctx context.Context, payload *CreatedPayload) { + eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, CreatedEvent, payload) + if err != nil { + log.Ctx(ctx).Err(err).Msgf("failed to send pipeline created event") + return + } + + log.Ctx(ctx).Debug().Msgf("reported pipeline created event with id '%s'", eventID) +} + +func (r *Reader) RegisterCreated(fn events.HandlerFunc[*CreatedPayload], + opts ...events.HandlerOption) error { + return events.ReaderRegisterEvent(r.innerReader, CreatedEvent, fn, opts...) +} diff --git a/app/events/pipeline/events.go b/app/events/pipeline/events.go new file mode 100644 index 0000000000..829e9e124c --- /dev/null +++ b/app/events/pipeline/events.go @@ -0,0 +1,20 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +const ( + // category defines the event category used for this package. + category = "pipeline" +) diff --git a/app/events/pipeline/execute.go b/app/events/pipeline/execute.go new file mode 100644 index 0000000000..ca220fb56b --- /dev/null +++ b/app/events/pipeline/execute.go @@ -0,0 +1,48 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import ( + "context" + + "github.com/harness/gitness/events" + "github.com/harness/gitness/types/enum" + + "github.com/rs/zerolog/log" +) + +const ExecutedEvent events.EventType = "executed" + +type ExecutedPayload struct { + PipelineID int64 `json:"pipeline_id"` + RepoID int64 `json:"repo_id"` + ExecutionNum int64 `json:"execution_number"` + Status enum.CIStatus `json:"status"` +} + +func (r *Reporter) Executed(ctx context.Context, payload *ExecutedPayload) { + eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, ExecutedEvent, payload) + if err != nil { + log.Ctx(ctx).Err(err).Msgf("failed to send pipeline executed event") + return + } + + log.Ctx(ctx).Debug().Msgf("reported pipeline executed event with id '%s'", eventID) +} + +func (r *Reader) RegisterExecuted(fn events.HandlerFunc[*ExecutedPayload], + opts ...events.HandlerOption) error { + return events.ReaderRegisterEvent(r.innerReader, ExecutedEvent, fn, opts...) +} diff --git a/app/events/pipeline/reader.go b/app/events/pipeline/reader.go new file mode 100644 index 0000000000..0cf3ffa2f7 --- /dev/null +++ b/app/events/pipeline/reader.go @@ -0,0 +1,38 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import "github.com/harness/gitness/events" + +func NewReaderFactory(eventsSystem *events.System) (*events.ReaderFactory[*Reader], error) { + readerFactoryFunc := func(innerReader *events.GenericReader) (*Reader, error) { + return &Reader{ + innerReader: innerReader, + }, nil + } + + return events.NewReaderFactory(eventsSystem, category, readerFactoryFunc) +} + +// Reader is the event reader for this package. +// It exposes typesafe event registration methods for all events by this package. +// NOTE: Event registration methods are in the event's dedicated file. +type Reader struct { + innerReader *events.GenericReader +} + +func (r *Reader) Configure(opts ...events.ReaderOption) { + r.innerReader.Configure(opts...) +} diff --git a/app/events/pipeline/reporter.go b/app/events/pipeline/reporter.go new file mode 100644 index 0000000000..a00e357fb1 --- /dev/null +++ b/app/events/pipeline/reporter.go @@ -0,0 +1,39 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import ( + "errors" + + "github.com/harness/gitness/events" +) + +// Reporter is the event reporter for this package. +// It exposes typesafe send methods for all events of this package. +// NOTE: Event send methods are in the event's dedicated file. +type Reporter struct { + innerReporter *events.GenericReporter +} + +func NewReporter(eventsSystem *events.System) (*Reporter, error) { + innerReporter, err := events.NewReporter(eventsSystem, category) + if err != nil { + return nil, errors.New("failed to create new GenericReporter from event system") + } + + return &Reporter{ + innerReporter: innerReporter, + }, nil +} diff --git a/app/events/pipeline/update.go b/app/events/pipeline/update.go new file mode 100644 index 0000000000..e89d0dca75 --- /dev/null +++ b/app/events/pipeline/update.go @@ -0,0 +1,45 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import ( + "context" + + "github.com/harness/gitness/events" + + "github.com/rs/zerolog/log" +) + +const UpdatedEvent events.EventType = "updated" + +type UpdatedPayload struct { + PipelineID int64 `json:"pipeline_id"` + RepoID int64 `json:"repo_id"` +} + +func (r *Reporter) Updated(ctx context.Context, payload *UpdatedPayload) { + eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, UpdatedEvent, payload) + if err != nil { + log.Ctx(ctx).Err(err).Msgf("failed to send pipeline updated event") + return + } + + log.Ctx(ctx).Debug().Msgf("reported pipeline update event with id '%s'", eventID) +} + +func (r *Reader) RegisterUpdated(fn events.HandlerFunc[*UpdatedPayload], + opts ...events.HandlerOption) error { + return events.ReaderRegisterEvent(r.innerReader, UpdatedEvent, fn, opts...) +} diff --git a/app/events/pipeline/wire.go b/app/events/pipeline/wire.go new file mode 100644 index 0000000000..215800d794 --- /dev/null +++ b/app/events/pipeline/wire.go @@ -0,0 +1,35 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package events + +import ( + "github.com/harness/gitness/events" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideReaderFactory, + ProvideReporter, +) + +func ProvideReaderFactory(eventsSystem *events.System) (*events.ReaderFactory[*Reader], error) { + return NewReaderFactory(eventsSystem) +} + +func ProvideReporter(eventsSystem *events.System) (*Reporter, error) { + return NewReporter(eventsSystem) +} diff --git a/app/pipeline/converter/jsonnet/jsonnet.go b/app/pipeline/converter/jsonnet/jsonnet.go index f1e6f485c5..4b8655278b 100644 --- a/app/pipeline/converter/jsonnet/jsonnet.go +++ b/app/pipeline/converter/jsonnet/jsonnet.go @@ -163,8 +163,8 @@ func Parse( // Since we want to maintain compatibility with drone, the older format // needs to be maintained (even if the variables do not exist in gitness). func mapBuild(v *types.Execution, vm *jsonnet.VM) { - vm.ExtVar(build+"event", v.Event) - vm.ExtVar(build+"action", v.Action) + vm.ExtVar(build+"event", string(v.Event)) + vm.ExtVar(build+"action", string(v.Action)) vm.ExtVar(build+"environment", v.Deploy) vm.ExtVar(build+"link", v.Link) vm.ExtVar(build+"branch", v.Target) diff --git a/app/pipeline/manager/convert.go b/app/pipeline/manager/convert.go index 8c9bbd0904..1fd62de62c 100644 --- a/app/pipeline/manager/convert.go +++ b/app/pipeline/manager/convert.go @@ -177,8 +177,8 @@ func ConvertToDroneBuild(execution *types.Execution) *drone.Build { Parent: execution.Parent, Status: string(execution.Status), Error: execution.Error, - Event: execution.Event, - Action: execution.Action, + Event: string(execution.Event), + Action: string(execution.Action), Link: execution.Link, Timestamp: execution.Timestamp, Title: execution.Title, diff --git a/app/pipeline/manager/manager.go b/app/pipeline/manager/manager.go index 8673ebcfb6..bb2f1bf023 100644 --- a/app/pipeline/manager/manager.go +++ b/app/pipeline/manager/manager.go @@ -23,6 +23,7 @@ import ( "time" "github.com/harness/gitness/app/bootstrap" + events "github.com/harness/gitness/app/events/pipeline" "github.com/harness/gitness/app/jwt" "github.com/harness/gitness/app/pipeline/converter" "github.com/harness/gitness/app/pipeline/file" @@ -152,6 +153,8 @@ type Manager struct { // Webhook store.WebhookSender publicAccess publicaccess.Service + // events reporter + reporter events.Reporter } func New( @@ -172,6 +175,7 @@ func New( stepStore store.StepStore, userStore store.PrincipalStore, publicAccess publicaccess.Service, + reporter events.Reporter, ) *Manager { return &Manager{ Config: config, @@ -191,6 +195,7 @@ func New( Steps: stepStore, Users: userStore, publicAccess: publicAccess, + reporter: reporter, } } @@ -481,6 +486,7 @@ func (m *Manager) AfterStage(_ context.Context, stage *types.Stage) error { Scheduler: m.Scheduler, Steps: m.Steps, Stages: m.Stages, + Reporter: m.reporter, } return t.do(noContext, stage) } diff --git a/app/pipeline/manager/teardown.go b/app/pipeline/manager/teardown.go index 957546f95f..3db268e315 100644 --- a/app/pipeline/manager/teardown.go +++ b/app/pipeline/manager/teardown.go @@ -20,6 +20,7 @@ import ( "strings" "time" + events "github.com/harness/gitness/app/events/pipeline" "github.com/harness/gitness/app/pipeline/checks" "github.com/harness/gitness/app/pipeline/scheduler" "github.com/harness/gitness/app/sse" @@ -43,6 +44,7 @@ type teardown struct { Repos store.RepoStore Steps store.StepStore Stages store.StageStore + Reporter events.Reporter } //nolint:gocognit // refactor if needed. @@ -174,6 +176,9 @@ func (t *teardown) do(ctx context.Context, stage *types.Stage) error { Msg("manager: could not publish execution completed event") } + // send pipeline execution status + t.reportExecutionCompleted(ctx, execution) + pipeline, err := t.Pipelines.Find(ctx, execution.PipelineID) if err != nil { log.Error().Err(err).Msg("manager: cannot find pipeline") @@ -364,3 +369,12 @@ func (t *teardown) resync(ctx context.Context, stage *types.Stage) error { stage.Stopped = updated.Stopped return nil } + +func (t *teardown) reportExecutionCompleted(ctx context.Context, execution *types.Execution) { + t.Reporter.Executed(ctx, &events.ExecutedPayload{ + PipelineID: execution.PipelineID, + RepoID: execution.RepoID, + ExecutionNum: execution.Number, + Status: execution.Status, + }) +} diff --git a/app/pipeline/manager/wire.go b/app/pipeline/manager/wire.go index 1a9d47dda4..8e7516808d 100644 --- a/app/pipeline/manager/wire.go +++ b/app/pipeline/manager/wire.go @@ -15,6 +15,7 @@ package manager import ( + events "github.com/harness/gitness/app/events/pipeline" "github.com/harness/gitness/app/pipeline/converter" "github.com/harness/gitness/app/pipeline/file" "github.com/harness/gitness/app/pipeline/scheduler" @@ -54,9 +55,11 @@ func ProvideExecutionManager( stepStore store.StepStore, userStore store.PrincipalStore, publicAccess publicaccess.Service, + reporter *events.Reporter, ) ExecutionManager { return New(config, executionStore, pipelineStore, urlProvider, sseStreamer, fileService, converterService, - logStore, logStream, checkStore, repoStore, scheduler, secretStore, stageStore, stepStore, userStore, publicAccess) + logStore, logStream, checkStore, repoStore, scheduler, secretStore, + stageStore, stepStore, userStore, publicAccess, *reporter) } // ProvideExecutionClient provides a client implementation to interact with the execution manager. diff --git a/app/pipeline/triggerer/trigger.go b/app/pipeline/triggerer/trigger.go index 5b5cfa7ec6..bed3ce8753 100644 --- a/app/pipeline/triggerer/trigger.go +++ b/app/pipeline/triggerer/trigger.go @@ -150,7 +150,7 @@ func (t *triggerer) Trigger( } }() - event := string(base.Action.GetTriggerEvent()) + event := base.Action.GetTriggerEvent() repo, err := t.repoStore.Find(ctx, pipeline.RepoID) if err != nil { @@ -178,7 +178,7 @@ func (t *triggerer) Trigger( Parent: base.Parent, Status: enum.CIStatusPending, Event: event, - Action: string(base.Action), + Action: base.Action, Link: base.Link, // Timestamp: base.Timestamp, Title: trunc(base.Title, 2000), @@ -254,7 +254,7 @@ func (t *triggerer) Trigger( switch { case skipBranch(pipeline, base.Target): log.Info().Str("pipeline", name).Msg("trigger: skipping pipeline, does not match branch") - case skipEvent(pipeline, event): + case skipEvent(pipeline, string(event)): log.Info().Str("pipeline", name).Msg("trigger: skipping pipeline, does not match event") case skipAction(pipeline, string(base.Action)): log.Info().Str("pipeline", name).Msg("trigger: skipping pipeline, does not match action") @@ -557,8 +557,8 @@ func (t *triggerer) createExecutionWithError( Parent: base.Parent, Status: enum.CIStatusError, Error: message, - Event: string(base.Action.GetTriggerEvent()), - Action: string(base.Action), + Event: base.Action.GetTriggerEvent(), + Action: base.Action, Link: base.Link, Title: base.Title, Message: base.Message, diff --git a/app/router/api.go b/app/router/api.go index 5582c2ec3d..fa9015e290 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -19,6 +19,8 @@ import ( "fmt" "net/http" + "github.com/harness/gitness/app/api/controller/aiagent" + "github.com/harness/gitness/app/api/controller/capabilities" "github.com/harness/gitness/app/api/controller/check" "github.com/harness/gitness/app/api/controller/connector" "github.com/harness/gitness/app/api/controller/execution" @@ -44,6 +46,8 @@ import ( "github.com/harness/gitness/app/api/controller/user" "github.com/harness/gitness/app/api/controller/webhook" "github.com/harness/gitness/app/api/handler/account" + handleraiagent "github.com/harness/gitness/app/api/handler/aiagent" + handlercapabilities "github.com/harness/gitness/app/api/handler/capabilities" handlercheck "github.com/harness/gitness/app/api/handler/check" handlerconnector "github.com/harness/gitness/app/api/handler/connector" handlerexecution "github.com/harness/gitness/app/api/handler/execution" @@ -93,8 +97,8 @@ import ( var ( // terminatedPathPrefixesAPI is the list of prefixes that will require resolving terminated paths. terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/", - "/v1/secrets/", "/v1/connectors", "/v1/templates/step", "/v1/templates/stage", - "/v1/gitspaces", "/v1/infraproviders", "/v1/migrate/repos"} + "/v1/secrets/", "/v1/connectors", "/v1/templates/step", "/v1/templates/stage", "/v1/gitspaces", "/v1/infraproviders", + "/v1/migrate/repos", "/v1/pipelines"} ) // NewAPIHandler returns a new APIHandler. @@ -127,6 +131,8 @@ func NewAPIHandler( infraProviderCtrl *infraprovider.Controller, migrateCtrl *migrate.Controller, gitspaceCtrl *gitspace.Controller, + aiagentCtrl *aiagent.Controller, + capabilitiesCtrl *capabilities.Controller, ) http.Handler { // Use go-chi router for inner routing. r := chi.NewRouter() @@ -159,7 +165,7 @@ func NewAPIHandler( setupRoutesV1WithAuth(r, appCtx, config, repoCtrl, repoSettingsCtrl, executionCtrl, triggerCtrl, logCtrl, pipelineCtrl, connectorCtrl, templateCtrl, pluginCtrl, secretCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, git, saCtrl, userCtrl, principalCtrl, checkCtrl, uploadCtrl, - searchCtrl, gitspaceCtrl, infraProviderCtrl, migrateCtrl) + searchCtrl, gitspaceCtrl, infraProviderCtrl, migrateCtrl, aiagentCtrl, capabilitiesCtrl) }) }) @@ -208,6 +214,8 @@ func setupRoutesV1WithAuth(r chi.Router, gitspaceCtrl *gitspace.Controller, infraProviderCtrl *infraprovider.Controller, migrateCtrl *migrate.Controller, + aiagentCtrl *aiagent.Controller, + capabilitiesCtrl *capabilities.Controller, ) { setupAccountWithAuth(r, userCtrl, config) setupSpaces(r, appCtx, spaceCtrl) @@ -216,6 +224,7 @@ func setupRoutesV1WithAuth(r chi.Router, setupConnectors(r, connectorCtrl) setupTemplates(r, templateCtrl) setupSecrets(r, secretCtrl) + setupAiAgent(r, aiagentCtrl, capabilitiesCtrl) setupUser(r, userCtrl) setupServiceAccounts(r, saCtrl) setupPrincipals(r, principalCtrl) @@ -253,6 +262,7 @@ func setupSpaces( r.Post("/import", handlerspace.HandleImportRepositories(spaceCtrl)) r.Post("/move", handlerspace.HandleMove(spaceCtrl)) r.Get("/spaces", handlerspace.HandleListSpaces(spaceCtrl)) + r.Get("/pipelines", handlerspace.HandleListPipelines(spaceCtrl)) r.Get("/repos", handlerspace.HandleListRepos(spaceCtrl)) r.Get("/service-accounts", handlerspace.HandleListServiceAccounts(spaceCtrl)) r.Get("/secrets", handlerspace.HandleListSecrets(spaceCtrl)) @@ -504,6 +514,16 @@ func setupTemplates( }) } +func setupAiAgent(r chi.Router, aiagentCtrl *aiagent.Controller, capabilitiesCtrl *capabilities.Controller) { + r.Route("/harness-intelligence", func(r chi.Router) { + r.Post("/generate-pipeline", handleraiagent.HandleGeneratePipeline(aiagentCtrl)) + r.Post("/update-pipeline", handleraiagent.HandleUpdatePipeline(aiagentCtrl)) + r.Post("/capabilities", handlercapabilities.HandleRunCapabilities(capabilitiesCtrl)) + r.Post("/suggest-pipeline", handleraiagent.HandleSuggestPipelines(aiagentCtrl)) + r.Post("/analyse-execution", handleraiagent.HandleAnalyse(aiagentCtrl)) + }) +} + func setupSecrets(r chi.Router, secretCtrl *secret.Controller) { r.Route("/secrets", func(r chi.Router) { // Create takes path and parentId via body, not uri diff --git a/app/router/wire.go b/app/router/wire.go index 4873b8a74b..e40e9aaf60 100644 --- a/app/router/wire.go +++ b/app/router/wire.go @@ -18,6 +18,8 @@ import ( "context" "strings" + "github.com/harness/gitness/app/api/controller/aiagent" + "github.com/harness/gitness/app/api/controller/capabilities" "github.com/harness/gitness/app/api/controller/check" "github.com/harness/gitness/app/api/controller/connector" "github.com/harness/gitness/app/api/controller/execution" @@ -100,6 +102,8 @@ func ProvideRouter( infraProviderCtrl *infraprovider.Controller, gitspaceCtrl *gitspace.Controller, migrateCtrl *migrate.Controller, + aiagentCtrl *aiagent.Controller, + capabilitiesCtrl *capabilities.Controller, urlProvider url.Provider, openapi openapi.Service, ) *Router { @@ -117,7 +121,7 @@ func ProvideRouter( authenticator, repoCtrl, repoSettingsCtrl, executionCtrl, logCtrl, spaceCtrl, pipelineCtrl, secretCtrl, triggerCtrl, connectorCtrl, templateCtrl, pluginCtrl, pullreqCtrl, webhookCtrl, githookCtrl, git, saCtrl, userCtrl, principalCtrl, checkCtrl, sysCtrl, blobCtrl, searchCtrl, - infraProviderCtrl, migrateCtrl, gitspaceCtrl) + infraProviderCtrl, migrateCtrl, gitspaceCtrl, aiagentCtrl, capabilitiesCtrl) routers[1] = NewAPIRouter(apiHandler) webHandler := NewWebHandler(config, authenticator, openapi) diff --git a/app/services/aiagent/aiagent.go b/app/services/aiagent/aiagent.go new file mode 100644 index 0000000000..5304cfb77f --- /dev/null +++ b/app/services/aiagent/aiagent.go @@ -0,0 +1,200 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + "context" + "fmt" + + capabilitiesctrl "github.com/harness/gitness/app/api/controller/capabilities" + "github.com/harness/gitness/app/auth/authz" + "github.com/harness/gitness/app/services/capabilities" + "github.com/harness/gitness/app/store" + "github.com/harness/gitness/genai" + "github.com/harness/gitness/git" + "github.com/harness/gitness/types" + capabilitytypes "github.com/harness/gitness/types/capabilities" + + "github.com/google/uuid" +) + +type HarnessIntelligence struct { + repoStore store.RepoStore + git git.Interface + authorizer authz.Authorizer + cr *capabilities.Registry + cc *capabilitiesctrl.Controller +} + +type Pipeline interface { + Generate(ctx context.Context, req *types.PipelineGenerateRequest) (*types.PipelineGenerateResponse, error) +} + +func capabilityResponseToChatContext( + ranCapabilities *capabilitiesctrl.CapabilityRunResponse) []capabilitytypes.AIContext { + var aiContexts []capabilitytypes.AIContext + for _, value := range ranCapabilities.CapabilitiesRan { + aiContext := capabilitytypes.AIContext{ + Type: capabilitytypes.AIContextPayloadType("other"), + Payload: value.Result, + Name: string(value.Type), + } + aiContexts = append(aiContexts, aiContext) + } + return aiContexts +} + +func (s *HarnessIntelligence) Generate( + ctx context.Context, req *types.PipelineGenerateRequest) (*types.PipelineGenerateResponse, error) { + if req.RepoRef == "" { + return nil, fmt.Errorf("no repo ref specified") + } + // do permission check on repo here? + repo, err := s.repoStore.FindByRef(ctx, req.RepoRef) + if err != nil { + return nil, fmt.Errorf("failed to find repo by ref: %w", err) + } + + conversationID := uuid.New() + chatRequest := &genai.ChatRequest{ + Prompt: req.Prompt, + ConversationID: conversationID.String(), + ConversationRaw: "", + Context: genai.GenerateAIContext( + genai.RepoRef{ + Ref: repo.Path, + }, + ), + Capabilities: s.cr.Capabilities(), + } + + resp, err := s.CapabilitiesLoop(ctx, chatRequest) + if err != nil { + return nil, err + } + + var yaml string + for _, value := range resp.Context { + out, ok := value.Payload.(*capabilities.ReturnPipelineYamlOutput) + if ok { + yaml = out.Yaml + } + } + return &types.PipelineGenerateResponse{ + YAML: yaml, + }, nil +} + +// TODO fix naming +type PipelineYaml struct { + Yaml string `yaml:"yaml"` +} + +// CapabilitiesLoop TODO: this should be replaced with an async model for Harness Enterprise, but remain for gitness. +func (s *HarnessIntelligence) CapabilitiesLoop( + ctx context.Context, req *genai.ChatRequest) (*genai.ChatRequest, error) { + returnToUser := false + for !returnToUser { + capToRun, err := genai.CallAIFoundation(ctx, s.cr, req) + if err != nil { + return nil, fmt.Errorf("failed to call local chat: %w", err) + } + + resp, err := s.cc.RunCapabilities(ctx, capToRun) + if err != nil { + return nil, fmt.Errorf("failed to run capabilities: %w", err) + } + + prevChatRequest := req + req = &genai.ChatRequest{ + Prompt: "", + ConversationID: prevChatRequest.ConversationID, + ConversationRaw: capToRun.ConversationRaw, + Context: capabilityResponseToChatContext(resp), + Capabilities: s.cr.Capabilities(), + } + + for _, value := range resp.CapabilitiesRan { + if value.ReturnToUser { + returnToUser = true + } + } + } + return req, nil +} + +func (s *HarnessIntelligence) Update( + ctx context.Context, + req *types.PipelineUpdateRequest) (*types.PipelineUpdateResponse, error) { + if req.RepoRef == "" { + return nil, fmt.Errorf("no repo ref specified") + } + // do permission check on repo here? + repo, err := s.repoStore.FindByRef(ctx, req.RepoRef) + if err != nil { + return nil, fmt.Errorf("failed to find repo by ref: %w", err) + } + + conversationID := uuid.New() + chatRequest := &genai.ChatRequest{ + Prompt: req.Prompt, + ConversationID: conversationID.String(), + ConversationRaw: "", + Context: genai.GenerateAIContext( + genai.RepoRef{ + Ref: repo.Path, + }, + genai.PipelineContext{ + Yaml: req.Pipeline, + }, + ), + Capabilities: s.cr.Capabilities(), + } + + resp, err := s.CapabilitiesLoop(ctx, chatRequest) + if err != nil { + return nil, err + } + + var yaml string + for _, value := range resp.Context { + out, ok := value.Payload.(*capabilities.ReturnPipelineYamlOutput) + if ok { + yaml = out.Yaml + } + } + + updateResponse := &types.PipelineUpdateResponse{ + YAML: yaml, + } + return updateResponse, nil +} + +func (s *HarnessIntelligence) Suggest( + _ context.Context, + _ *types.PipelineSuggestionsRequest) (*types.PipelineSuggestionsResponse, error) { + return &types.PipelineSuggestionsResponse{ + Suggestions: []types.Suggestion{ + { + ID: "add-testing-stage", + Prompt: "add a testing stage", + UserSuggestion: "You should follow best practices by adding a testing stage", + Suggestion: "kind: pipeline\nstages:\n - steps:\n - name: build\n " + + "timeout: 10m\n run:\n script: go build\n - run:\n " + + "script: go test\n on-failure:\n errors: all\n action: ignore", + }, + }, + }, nil +} diff --git a/app/services/aiagent/wire.go b/app/services/aiagent/wire.go new file mode 100644 index 0000000000..715d9afd9b --- /dev/null +++ b/app/services/aiagent/wire.go @@ -0,0 +1,45 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aiagent + +import ( + capabilitiesctrl "github.com/harness/gitness/app/api/controller/capabilities" + "github.com/harness/gitness/app/auth/authz" + "github.com/harness/gitness/app/services/capabilities" + "github.com/harness/gitness/app/store" + "github.com/harness/gitness/git" + + "github.com/google/wire" +) + +var WireSet = wire.NewSet( + ProvideAiAgent, +) + +func ProvideAiAgent( + repoStore store.RepoStore, + git git.Interface, + authorizer authz.Authorizer, + cr *capabilities.Registry, + cc *capabilitiesctrl.Controller, +) (*HarnessIntelligence, error) { + return &HarnessIntelligence{ + repoStore, + git, + authorizer, + cr, + cc, + }, nil +} diff --git a/app/services/capabilities/capabilities.go b/app/services/capabilities/capabilities.go new file mode 100644 index 0000000000..dc289327c5 --- /dev/null +++ b/app/services/capabilities/capabilities.go @@ -0,0 +1,124 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/harness/gitness/types/capabilities" +) + +// newLogic This function helps us adhere to the same function definition, across different capabilities +// Each capability takes in an input and returns an output, by using this function, each capability can have its own +// input and output definition. +func newLogic[T capabilities.Input, U capabilities.Output]( + logic func(ctx context.Context, input T) (U, error)) capabilities.Logic { + return func(ctx context.Context, input capabilities.Input) (capabilities.Output, error) { + myInput, ok := input.(T) + if !ok { + return nil, fmt.Errorf("invalid input type") + } + return logic(ctx, myInput) + } +} + +func NewRegistry() *Registry { + return &Registry{ + capabilities: make(map[capabilities.Type]capabilities.Capability), + } +} + +type Registry struct { + capabilities map[capabilities.Type]capabilities.Capability +} + +func (r *Registry) register(c capabilities.Capability) error { + if _, ok := r.capabilities[c.Type]; ok { + return fmt.Errorf("capability %q already registered", c.Type) + } + + r.capabilities[c.Type] = c + + return nil +} + +func (r *Registry) Exists(t capabilities.Type) bool { + _, ok := r.capabilities[t] + return ok +} + +func (r *Registry) ReturnToUser(t capabilities.Type) (bool, error) { + c, ok := r.capabilities[t] + if !ok { + return false, fmt.Errorf("unknown capability type %q", t) + } + return c.ReturnToUser, nil +} + +func (r *Registry) Get(t capabilities.Type) (capabilities.Capability, bool) { + c, ok := r.capabilities[t] + return c, ok +} + +func (r *Registry) Execute( + ctx context.Context, t capabilities.Type, in capabilities.Input) (capabilities.Output, error) { + c, ok := r.Get(t) + if !ok { + return nil, fmt.Errorf("unknown capability type %q", t) + } + + out, err := c.Logic(ctx, in) + if err != nil { + return nil, fmt.Errorf("failed execution: %w", err) + } + + return out, nil +} + +func DeserializeInput(cr *Registry, t capabilities.Type, raw json.RawMessage) (capabilities.Input, error) { + capability, ok := cr.Get(t) + if !ok { + return nil, fmt.Errorf("unknown type: %s", t) + } + + input := capability.NewInput() + + err := json.Unmarshal(raw, input) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal input: %w", err) + } + + return input, nil +} + +func (r *Registry) Capabilities() []capabilities.CapabilityReference { + var capabilitiesList []capabilities.CapabilityReference + for _, capability := range r.capabilities { + c := capabilities.CapabilityReference{ + Type: capability.Type, + Version: capability.Version, + } + capabilitiesList = append(capabilitiesList, c) + } + return capabilitiesList +} + +type RepoRef struct { + Ref string `json:"ref"` +} + +func (RepoRef) IsCapabilityOutput() {} diff --git a/app/services/capabilities/get_file.go b/app/services/capabilities/get_file.go new file mode 100644 index 0000000000..4d4942a5f8 --- /dev/null +++ b/app/services/capabilities/get_file.go @@ -0,0 +1,150 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "bytes" + "context" + "fmt" + "io" + "unicode/utf8" + + "github.com/harness/gitness/app/store" + "github.com/harness/gitness/git" + "github.com/harness/gitness/types/capabilities" + "github.com/harness/gitness/types/check" + + "github.com/rs/zerolog/log" +) + +const ( + // maxGetContentFileSize specifies the maximum number of bytes a file content response contains. + // If a file is any larger, the content is truncated. + maxGetContentFileSize = 1 << 22 // 4 MB +) + +var GetFileType capabilities.Type = "get_file" +var GetFileVersion capabilities.Version = "1.0.0" + +type GetFileInput struct { + RepoREF string `json:"repo_ref"` + GitREF string `json:"git_ref"` + Path string `json:"path"` +} + +func (GetFileInput) IsCapabilityInput() {} + +type GetFileOutput struct { + Content string `json:"content"` +} + +func (GetFileOutput) IsCapabilityOutput() {} + +func (GetFileOutput) GetName() string { + return string(GetFileType) +} + +const AIContextPayloadTypeGetFile capabilities.AIContextPayloadType = "other" + +func (GetFileOutput) GetType() capabilities.AIContextPayloadType { + return AIContextPayloadTypeGetFile +} + +func (r *Registry) RegisterGetFileCapability( + logic func(ctx context.Context, input *GetFileInput) (*GetFileOutput, error), +) error { + return r.register( + capabilities.Capability{ + Type: GetFileType, + NewInput: func() capabilities.Input { return &GetFileInput{} }, + Logic: newLogic(logic), + }, + ) +} + +func GetFile( + repoStore store.RepoStore, + gitI git.Interface) func(ctx context.Context, input *GetFileInput) (*GetFileOutput, error) { + return func(ctx context.Context, input *GetFileInput) (*GetFileOutput, error) { + if input.RepoREF == "" { + return nil, check.NewValidationError("repo_ref is required") + } + if input.Path == "" { + return nil, check.NewValidationError("path is required") + } + + repo, err := repoStore.FindByRef(ctx, input.RepoREF) + if err != nil { + return nil, fmt.Errorf("failed to find repo %q: %w", input.RepoREF, err) + } + + // set gitRef to default branch in case an empty reference was provided + gitRef := input.GitREF + if gitRef == "" { + gitRef = repo.DefaultBranch + } + + readParams := git.CreateReadParams(repo) + node, err := gitI.GetTreeNode(ctx, &git.GetTreeNodeParams{ + ReadParams: readParams, + GitREF: gitRef, + Path: input.Path, + IncludeLatestCommit: false, + }) + if err != nil { + return nil, fmt.Errorf("failed to read tree node: %w", err) + } + + // Todo: Handle symlinks + return getContent(ctx, gitI, readParams, node) + } +} + +func getContent( + ctx context.Context, + gitI git.Interface, + readParams git.ReadParams, + node *git.GetTreeNodeOutput) (*GetFileOutput, error) { + output, err := gitI.GetBlob(ctx, &git.GetBlobParams{ + ReadParams: readParams, + SHA: node.Node.SHA, + SizeLimit: maxGetContentFileSize, + }) + if err != nil { + return nil, fmt.Errorf("failed to get file content: %w", err) + } + + defer func() { + if err := output.Content.Close(); err != nil { + log.Ctx(ctx).Warn().Err(err).Msgf("failed to close blob content reader.") + } + }() + + content, err := io.ReadAll(output.Content) + if err != nil { + return nil, fmt.Errorf("failed to read blob content: %w", err) + } + + // throw error for binary file + if i := bytes.IndexByte(content, '\x00'); i > 0 { + if !utf8.Valid(content[:i]) { + return nil, check.NewValidationError("file content is not valid UTF-8") + } + } + + return &GetFileOutput{ + Content: string(content), + }, nil +} diff --git a/app/services/capabilities/list_files.go b/app/services/capabilities/list_files.go new file mode 100644 index 0000000000..1decc574cf --- /dev/null +++ b/app/services/capabilities/list_files.go @@ -0,0 +1,124 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "context" + "fmt" + + "github.com/harness/gitness/app/store" + "github.com/harness/gitness/git" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/capabilities" + "github.com/harness/gitness/types/check" +) + +var ListFilesType capabilities.Type = "list_files" +var ListFilesVersion capabilities.Version = "0" + +type ListFilesInput struct { + RepoREF string `json:"repo_ref"` + GitRef string `json:"git_ref"` + Path string `json:"path"` +} + +func (ListFilesInput) IsCapabilityInput() {} + +type ListFilesOutput struct { + Files []string `json:"files"` + Directories []string `json:"directories"` +} + +func (ListFilesOutput) IsCapabilityOutput() {} + +const AIContextPayloadTypeListFiles capabilities.AIContextPayloadType = "other" + +func (ListFilesOutput) GetType() capabilities.AIContextPayloadType { + return AIContextPayloadTypeListFiles +} + +func (ListFilesOutput) GetName() string { + return string(ListFilesType) +} + +func (r *Registry) RegisterListFilesCapability( + logic func(ctx context.Context, input *ListFilesInput) (*ListFilesOutput, error), +) error { + return r.register( + capabilities.Capability{ + Type: ListFilesType, + NewInput: func() capabilities.Input { return &ListFilesInput{} }, + Logic: newLogic(logic), + Version: ListFilesVersion, + ReturnToUser: false, + }, + ) +} + +func ListFiles( + repoStore store.RepoStore, + gitI git.Interface) func( + ctx context.Context, + input *ListFilesInput) (*ListFilesOutput, error) { + return func(ctx context.Context, input *ListFilesInput) (*ListFilesOutput, error) { + if input.RepoREF == "" { + return nil, check.NewValidationError("repo_ref is required") + } + + repo, err := repoStore.FindByRef(ctx, input.RepoREF) + if err != nil { + return nil, fmt.Errorf("failed to find repo %q: %w", input.RepoREF, err) + } + + // set gitRef to default branch in case an empty reference was provided + gitRef := input.GitRef + if gitRef == "" { + gitRef = repo.DefaultBranch + } + + return listFiles(ctx, gitI, repo, gitRef, input.Path) + } +} + +func listFiles( + ctx context.Context, + gitI git.Interface, + repo *types.Repository, + gitRef string, + path string) (*ListFilesOutput, error) { + files := make([]string, 0) + directories := make([]string, 0) + tree, err := gitI.ListTreeNodes(ctx, &git.ListTreeNodeParams{ + ReadParams: git.CreateReadParams(repo), + GitREF: gitRef, + Path: path, + IncludeLatestCommit: false, + }) + if err != nil { + return nil, fmt.Errorf("failed to list tree nodes: %w", err) + } + for _, v := range tree.Nodes { + if v.Type == git.TreeNodeTypeBlob { + files = append(files, v.Path) + } else if v.Type == git.TreeNodeTypeTree { + directories = append(directories, v.Path) + } + } + + return &ListFilesOutput{ + Files: files, + Directories: directories, + }, nil +} diff --git a/app/services/capabilities/return_pipeline_yaml.go b/app/services/capabilities/return_pipeline_yaml.go new file mode 100644 index 0000000000..d08e26f37b --- /dev/null +++ b/app/services/capabilities/return_pipeline_yaml.go @@ -0,0 +1,69 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "context" + + "github.com/harness/gitness/types/capabilities" +) + +var ReturnPipelineYamlType capabilities.Type = "return_pipeline_yaml" +var ReturnPipelineYamlVersion capabilities.Version = "0" + +type ReturnPipelineYamlInput struct { + Yaml string `json:"pipeline_yaml"` +} + +func (ReturnPipelineYamlInput) IsCapabilityInput() {} + +type ReturnPipelineYamlOutput struct { + Yaml string `json:"pipeline_yaml"` +} + +func (ReturnPipelineYamlOutput) IsCapabilityOutput() {} + +const AIContextPayloadTypeReturnPipelineYaml capabilities.AIContextPayloadType = "other" + +func (ReturnPipelineYamlOutput) GetType() capabilities.AIContextPayloadType { + return AIContextPayloadTypeReturnPipelineYaml +} + +func (ReturnPipelineYamlOutput) GetName() string { + return string(ReturnPipelineYamlType) +} + +func (r *Registry) RegisterReturnPipelineYamlCapability( + logic func(ctx context.Context, input *ReturnPipelineYamlInput) (*ReturnPipelineYamlOutput, error), +) error { + return r.register( + capabilities.Capability{ + Type: ReturnPipelineYamlType, + NewInput: func() capabilities.Input { return &ReturnPipelineYamlInput{} }, + Logic: newLogic(logic), + Version: ReturnPipelineYamlVersion, + ReturnToUser: true, + }, + ) +} + +// ReturnPipelineYaml could take in, eg repoStore store.RepoStore, git git.Interface, as arguments. +func ReturnPipelineYaml() func(ctx context.Context, input *ReturnPipelineYamlInput) (*ReturnPipelineYamlOutput, error) { + return func(_ context.Context, input *ReturnPipelineYamlInput) (*ReturnPipelineYamlOutput, error) { + return &ReturnPipelineYamlOutput{ + Yaml: input.Yaml, + }, nil + } +} diff --git a/app/services/capabilities/wire.go b/app/services/capabilities/wire.go new file mode 100644 index 0000000000..c052fd9756 --- /dev/null +++ b/app/services/capabilities/wire.go @@ -0,0 +1,40 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import ( + "github.com/harness/gitness/app/store" + "github.com/harness/gitness/git" + + "github.com/google/wire" +) + +var WireSet = wire.NewSet( + ProvideCapabilities, +) + +func panicOnErr(err error) { + if err != nil { + panic(err) + } +} + +func ProvideCapabilities(repoStore store.RepoStore, git git.Interface) (*Registry, error) { + registry := NewRegistry() + panicOnErr(registry.RegisterListFilesCapability(ListFiles(repoStore, git))) + panicOnErr(registry.RegisterGetFileCapability(GetFile(repoStore, git))) + panicOnErr(registry.RegisterReturnPipelineYamlCapability(ReturnPipelineYaml())) + return registry, nil +} diff --git a/app/store/database.go b/app/store/database.go index 82c05b7e0e..02e5632697 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -698,6 +698,12 @@ type ( // IncrementSeqNum increments the sequence number of the pipeline IncrementSeqNum(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) + + // ListInSpace lists pipelines in a particular space. + ListInSpace(ctx context.Context, spaceID int64, filter types.ListQueryFilter) ([]*types.Pipeline, error) + + // CountInSpace counts pipelines in a particular space. + CountInSpace(ctx context.Context, spaceID int64, filter types.ListQueryFilter) (int64, error) } SecretStore interface { diff --git a/app/store/database/execution.go b/app/store/database/execution.go index b3c977ee80..fae812a651 100644 --- a/app/store/database/execution.go +++ b/app/store/database/execution.go @@ -55,8 +55,8 @@ type execution struct { Parent int64 `db:"execution_parent"` Status enum.CIStatus `db:"execution_status"` Error string `db:"execution_error"` - Event string `db:"execution_event"` - Action string `db:"execution_action"` + Event enum.TriggerEvent `db:"execution_event"` + Action enum.TriggerAction `db:"execution_action"` Link string `db:"execution_link"` Timestamp int64 `db:"execution_timestamp"` Title string `db:"execution_title"` diff --git a/app/store/database/pipeline.go b/app/store/database/pipeline.go index 1d1c797b3f..9f1f7a14fa 100644 --- a/app/store/database/pipeline.go +++ b/app/store/database/pipeline.go @@ -216,6 +216,75 @@ func (s *pipelineStore) List( return dst, nil } +// ListInSpace lists all the pipelines for a space. +func (s *pipelineStore) ListInSpace( + ctx context.Context, + spaceID int64, + filter types.ListQueryFilter, +) ([]*types.Pipeline, error) { + const pipelineWithRepoColumns = pipelineColumns + ` + ,repositories.repo_id + ,repositories.repo_uid + ` + stmt := database.Builder. + Select(pipelineWithRepoColumns). + From("pipelines"). + InnerJoin("repositories ON pipelines.pipeline_repo_id = repositories.repo_id"). + Where("repositories.repo_parent_id = ?", fmt.Sprint(spaceID)) + + if filter.Query != "" { + stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query))) + } + + stmt = stmt.Limit(database.Limit(filter.Size)) + stmt = stmt.Offset(database.Offset(filter.Page, filter.Size)) + + sql, args, err := stmt.ToSql() + if err != nil { + return nil, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + dst := []*pipelineRepoJoin{} + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing custom list query") + } + + return convertPipelineRepoJoins(dst), nil +} + +// CountInSpace counts the number of pipelines in a space. +func (s *pipelineStore) CountInSpace( + ctx context.Context, + spaceID int64, + filter types.ListQueryFilter, +) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("pipelines"). + InnerJoin("repositories ON pipelines.pipeline_repo_id = repositories.repo_id"). + Where("repositories.repo_parent_id = ?", fmt.Sprint(spaceID)) + + if filter.Query != "" { + stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query))) + } + + var count int64 + sql, args, err := stmt.ToSql() + if err != nil { + return count, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(ctx, err, "Failed executing count query") + } + return count, nil +} + // ListLatest lists all the pipelines under a repository with information // about the latest build if available. func (s *pipelineStore) ListLatest( diff --git a/app/store/database/pipeline_join.go b/app/store/database/pipeline_join.go index cb32ab74dc..520099645f 100644 --- a/app/store/database/pipeline_join.go +++ b/app/store/database/pipeline_join.go @@ -68,7 +68,7 @@ func convertPipelineJoin(join *pipelineExecutionJoin) *types.Pipeline { ID: join.ID.Int64, PipelineID: join.PipelineID.Int64, RepoID: join.RepoID.Int64, - Action: join.Action.String, + Action: enum.TriggerAction(join.Action.String), Trigger: join.Trigger.String, Number: join.Number.Int64, After: join.After.String, @@ -92,3 +92,27 @@ func convertPipelineJoin(join *pipelineExecutionJoin) *types.Pipeline { } return ret } + +type pipelineRepoJoin struct { + *types.Pipeline + RepoID sql.NullInt64 `db:"repo_id"` + RepoUID sql.NullString `db:"repo_uid"` +} + +func convertPipelineRepoJoins(rows []*pipelineRepoJoin) []*types.Pipeline { + pipelines := []*types.Pipeline{} + for _, k := range rows { + pipeline := convertPipelineRepoJoin(k) + pipelines = append(pipelines, pipeline) + } + return pipelines +} + +func convertPipelineRepoJoin(join *pipelineRepoJoin) *types.Pipeline { + ret := join.Pipeline + if !join.RepoID.Valid { + return ret + } + ret.RepoUID = join.RepoUID.String + return ret +} diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index 23bfc1e825..190bfa660c 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -10,6 +10,8 @@ package main import ( "context" + "github.com/harness/gitness/app/api/controller/aiagent" + "github.com/harness/gitness/app/api/controller/capabilities" checkcontroller "github.com/harness/gitness/app/api/controller/check" "github.com/harness/gitness/app/api/controller/connector" "github.com/harness/gitness/app/api/controller/execution" @@ -43,6 +45,7 @@ import ( gitevents "github.com/harness/gitness/app/events/git" gitspaceevents "github.com/harness/gitness/app/events/gitspace" gitspaceinfraevents "github.com/harness/gitness/app/events/gitspaceinfra" + pipelineevents "github.com/harness/gitness/app/events/pipeline" pullreqevents "github.com/harness/gitness/app/events/pullreq" repoevents "github.com/harness/gitness/app/events/repo" infrastructure "github.com/harness/gitness/app/gitspace/infrastructure" @@ -64,6 +67,8 @@ import ( "github.com/harness/gitness/app/router" "github.com/harness/gitness/app/server" "github.com/harness/gitness/app/services" + aiagentservice "github.com/harness/gitness/app/services/aiagent" + capabilitiesservice "github.com/harness/gitness/app/services/capabilities" "github.com/harness/gitness/app/services/cleanup" "github.com/harness/gitness/app/services/codecomments" "github.com/harness/gitness/app/services/codeowners" @@ -153,6 +158,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e infrastructure.WireSet, infraproviderpkg.WireSet, gitspaceevents.WireSet, + pipelineevents.WireSet, infraproviderCtrl.WireSet, gitspaceCtrl.WireSet, gitevents.WireSet, @@ -240,6 +246,10 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e cliserver.ProvideGitspaceInfraProvisionerConfig, cliserver.ProvideIDEVSCodeConfig, instrument.WireSet, + aiagentservice.WireSet, + aiagent.WireSet, + capabilities.WireSet, + capabilitiesservice.WireSet, ) return &cliserver.System{}, nil } diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 0c21cbaf0b..5d2ff6d27f 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -9,6 +9,8 @@ package main import ( "context" + aiagent2 "github.com/harness/gitness/app/api/controller/aiagent" + capabilities2 "github.com/harness/gitness/app/api/controller/capabilities" check2 "github.com/harness/gitness/app/api/controller/check" "github.com/harness/gitness/app/api/controller/connector" "github.com/harness/gitness/app/api/controller/execution" @@ -39,10 +41,11 @@ import ( "github.com/harness/gitness/app/auth/authn" "github.com/harness/gitness/app/auth/authz" "github.com/harness/gitness/app/bootstrap" - events5 "github.com/harness/gitness/app/events/git" - events6 "github.com/harness/gitness/app/events/gitspace" + events6 "github.com/harness/gitness/app/events/git" + events7 "github.com/harness/gitness/app/events/gitspace" events3 "github.com/harness/gitness/app/events/gitspaceinfra" - events4 "github.com/harness/gitness/app/events/pullreq" + events4 "github.com/harness/gitness/app/events/pipeline" + events5 "github.com/harness/gitness/app/events/pullreq" events2 "github.com/harness/gitness/app/events/repo" "github.com/harness/gitness/app/gitspace/infrastructure" "github.com/harness/gitness/app/gitspace/logutil" @@ -63,6 +66,8 @@ import ( "github.com/harness/gitness/app/router" server2 "github.com/harness/gitness/app/server" "github.com/harness/gitness/app/services" + "github.com/harness/gitness/app/services/aiagent" + "github.com/harness/gitness/app/services/capabilities" "github.com/harness/gitness/app/services/cleanup" "github.com/harness/gitness/app/services/codecomments" "github.com/harness/gitness/app/services/codeowners" @@ -275,7 +280,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro infraproviderService := infraprovider2.ProvideInfraProvider(transactor, infraProviderResourceStore, infraProviderConfigStore, infraProviderTemplateStore, factory, spaceStore) gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, spaceStore, infraproviderService) spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService, labelService, instrumentService) - pipelineController := pipeline.ProvideController(repoStore, triggerStore, authorizer, pipelineStore) + reporter2, err := events4.ProvideReporter(eventsSystem) + if err != nil { + return nil, err + } + pipelineController := pipeline.ProvideController(repoStore, triggerStore, authorizer, pipelineStore, reporter2) secretController := secret.ProvideController(encrypter, secretStore, authorizer, spaceStore) triggerController := trigger.ProvideController(authorizer, triggerStore, pipelineStore, repoStore) connectorController := connector.ProvideController(connectorStore, authorizer, spaceStore) @@ -287,27 +296,27 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro pullReqReviewStore := database.ProvidePullReqReviewStore(db) pullReqReviewerStore := database.ProvidePullReqReviewerStore(db, principalInfoCache) pullReqFileViewStore := database.ProvidePullReqFileViewStore(db) - reporter2, err := events4.ProvideReporter(eventsSystem) + reporter3, err := events5.ProvideReporter(eventsSystem) if err != nil { return nil, err } migrator := codecomments.ProvideMigrator(gitInterface) - readerFactory, err := events5.ProvideReaderFactory(eventsSystem) + readerFactory, err := events6.ProvideReaderFactory(eventsSystem) if err != nil { return nil, err } - eventsReaderFactory, err := events4.ProvideReaderFactory(eventsSystem) + eventsReaderFactory, err := events5.ProvideReaderFactory(eventsSystem) if err != nil { return nil, err } repoGitInfoView := database.ProvideRepoGitInfoView(db) repoGitInfoCache := cache.ProvideRepoGitInfoCache(repoGitInfoView) - pullreqService, err := pullreq.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter2, gitInterface, repoGitInfoCache, repoStore, pullReqStore, pullReqActivityStore, principalInfoCache, codeCommentView, migrator, pullReqFileViewStore, pubSub, provider, streamer) + pullreqService, err := pullreq.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter3, gitInterface, repoGitInfoCache, repoStore, pullReqStore, pullReqActivityStore, principalInfoCache, codeCommentView, migrator, pullReqFileViewStore, pubSub, provider, streamer) if err != nil { return nil, err } pullReq := migrate.ProvidePullReqImporter(provider, gitInterface, principalStore, repoStore, pullReqStore, pullReqActivityStore, transactor) - pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, principalInfoCache, pullReqFileViewStore, membershipStore, checkStore, gitInterface, reporter2, migrator, pullreqService, protectionManager, streamer, codeownersService, lockerLocker, pullReq, labelService, instrumentService) + pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, principalInfoCache, pullReqFileViewStore, membershipStore, checkStore, gitInterface, reporter3, migrator, pullreqService, protectionManager, streamer, codeownersService, lockerLocker, pullReq, labelService, instrumentService) webhookConfig := server.ProvideWebhookConfig(config) webhookStore := database.ProvideWebhookStore(db) webhookExecutionStore := database.ProvideWebhookExecutionStore(db) @@ -316,7 +325,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro return nil, err } webhookController := webhook2.ProvideController(webhookConfig, authorizer, webhookStore, webhookExecutionStore, repoStore, webhookService, encrypter) - reporter3, err := events5.ProvideReporter(eventsSystem) + reporter4, err := events6.ProvideReporter(eventsSystem) if err != nil { return nil, err } @@ -332,7 +341,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - githookController := githook.ProvideController(authorizer, principalStore, repoStore, reporter3, reporter, gitInterface, pullReqStore, provider, protectionManager, clientFactory, resourceLimiter, settingsService, preReceiveExtender, updateExtender, postReceiveExtender) + githookController := githook.ProvideController(authorizer, principalStore, repoStore, reporter4, reporter, gitInterface, pullReqStore, provider, protectionManager, clientFactory, resourceLimiter, settingsService, preReceiveExtender, updateExtender, postReceiveExtender) serviceaccountController := serviceaccount.NewController(principalUID, authorizer, principalStore, spaceStore, repoStore, tokenStore) principalController := principal.ProvideController(principalStore, authorizer) v := check2.ProvideCheckSanitizers() @@ -350,7 +359,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro searcher := keywordsearch.ProvideSearcher(localIndexSearcher) keywordsearchController := keywordsearch2.ProvideController(authorizer, searcher, repoController, spaceController) infraproviderController := infraprovider3.ProvideController(authorizer, spaceStore, infraproviderService) - reporter4, err := events6.ProvideReporter(eventsSystem) + reporter5, err := events7.ProvideReporter(eventsSystem) if err != nil { return nil, err } @@ -370,18 +379,28 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro vsCodeWeb := ide.ProvideVSCodeWebService(vsCodeWebConfig) passwordResolver := secret2.ProvidePasswordResolver() resolverFactory := secret2.ProvideResolverFactory(passwordResolver) - orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, infraProviderResourceStore, infraProvisioner, containerOrchestrator, reporter4, orchestratorConfig, vsCode, vsCodeWeb, resolverFactory) + orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, infraProviderResourceStore, infraProvisioner, containerOrchestrator, reporter5, orchestratorConfig, vsCode, vsCodeWeb, resolverFactory) gitspaceEventStore := database.ProvideGitspaceEventStore(db) - gitspaceController := gitspace2.ProvideController(transactor, authorizer, infraproviderService, gitspaceConfigStore, gitspaceInstanceStore, spaceStore, reporter4, orchestratorOrchestrator, gitspaceEventStore, statefulLogger, scmSCM, repoStore, gitspaceService) + gitspaceController := gitspace2.ProvideController(transactor, authorizer, infraproviderService, gitspaceConfigStore, gitspaceInstanceStore, spaceStore, reporter5, orchestratorOrchestrator, gitspaceEventStore, statefulLogger, scmSCM, repoStore, gitspaceService) rule := migrate.ProvideRuleImporter(ruleStore, transactor, principalStore) migrateWebhook := migrate.ProvideWebhookImporter(webhookConfig, transactor, webhookStore) migrateController := migrate2.ProvideController(authorizer, publicaccessService, gitInterface, provider, pullReq, rule, migrateWebhook, resourceLimiter, auditService, repoIdentifier, transactor, spaceStore, repoStore) + registry, err := capabilities.ProvideCapabilities(repoStore, gitInterface) + if err != nil { + return nil, err + } + capabilitiesController := capabilities2.ProvideController(registry) + harnessIntelligence, err := aiagent.ProvideAiAgent(repoStore, gitInterface, authorizer, registry, capabilitiesController) + if err != nil { + return nil, err + } + aiagentController := aiagent2.ProvideController(authorizer, harnessIntelligence, repoStore, pipelineStore, executionStore) openapiService := openapi.ProvideOpenAPIService() - routerRouter := router.ProvideRouter(ctx, config, authenticator, repoController, reposettingsController, executionController, logsController, spaceController, pipelineController, secretController, triggerController, connectorController, templateController, pluginController, pullreqController, webhookController, githookController, gitInterface, serviceaccountController, controller, principalController, checkController, systemController, uploadController, keywordsearchController, infraproviderController, gitspaceController, migrateController, provider, openapiService) + routerRouter := router.ProvideRouter(ctx, config, authenticator, repoController, reposettingsController, executionController, logsController, spaceController, pipelineController, secretController, triggerController, connectorController, templateController, pluginController, pullreqController, webhookController, githookController, gitInterface, serviceaccountController, controller, principalController, checkController, systemController, uploadController, keywordsearchController, infraproviderController, gitspaceController, migrateController, aiagentController, capabilitiesController, provider, openapiService) serverServer := server2.ProvideServer(config, routerRouter) publickeyService := publickey.ProvidePublicKey(publicKeyStore, principalInfoCache) sshServer := ssh.ProvideServer(config, publickeyService, repoController) - executionManager := manager.ProvideExecutionManager(config, executionStore, pipelineStore, provider, streamer, fileService, converterService, logStore, logStream, checkStore, repoStore, schedulerScheduler, secretStore, stageStore, stepStore, principalStore, publicaccessService) + executionManager := manager.ProvideExecutionManager(config, executionStore, pipelineStore, provider, streamer, fileService, converterService, logStore, logStream, checkStore, repoStore, schedulerScheduler, secretStore, stageStore, stepStore, principalStore, publicaccessService, reporter2) client := manager.ProvideExecutionClient(executionManager, provider, config) resolverManager := resolver.ProvideResolver(config, pluginStore, templateStore, executionStore, repoStore) runtimeRunner, err := runner.ProvideExecutionRunner(config, client, resolverManager) @@ -428,7 +447,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro return nil, err } gitspaceeventConfig := server.ProvideGitspaceEventConfig(config) - readerFactory3, err := events6.ProvideReaderFactory(eventsSystem) + readerFactory3, err := events7.ProvideReaderFactory(eventsSystem) if err != nil { return nil, err } @@ -440,7 +459,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - gitspaceinfraeventService, err := gitspaceinfraevent.ProvideService(ctx, gitspaceeventConfig, readerFactory4, orchestratorOrchestrator, gitspaceService, reporter4) + gitspaceinfraeventService, err := gitspaceinfraevent.ProvideService(ctx, gitspaceeventConfig, readerFactory4, orchestratorOrchestrator, gitspaceService, reporter5) if err != nil { return nil, err } diff --git a/genai/genai.go b/genai/genai.go new file mode 100644 index 0000000000..9907a27989 --- /dev/null +++ b/genai/genai.go @@ -0,0 +1,114 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package genai + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "os" + + capabilitiesctrl "github.com/harness/gitness/app/api/controller/capabilities" + capabilitieshandler "github.com/harness/gitness/app/api/handler/capabilities" + "github.com/harness/gitness/app/services/capabilities" + capabilities2 "github.com/harness/gitness/types/capabilities" +) + +const ( + AIFoundationServiceToken = "AI_FOUNDATION_SERVICE_TOKEN" +) + +func GenerateAIContext(payloads ...capabilities2.AIContextPayload) []capabilities2.AIContext { + out := make([]capabilities2.AIContext, len(payloads)) + for i := range payloads { + out[i] = capabilities2.AIContext{ + Type: payloads[i].GetType(), + Payload: payloads[i], + Name: payloads[i].GetName(), + } + } + return out +} + +type PipelineContext struct { + Yaml string `json:"yaml"` +} + +func (c PipelineContext) GetName() string { + return "pipeline_context" +} + +const AIContextPayloadTypePipelineContext capabilities2.AIContextPayloadType = "other" + +func (PipelineContext) GetType() capabilities2.AIContextPayloadType { + return AIContextPayloadTypePipelineContext +} + +type RepoRef struct { + Ref string `json:"ref"` +} + +func (r RepoRef) GetName() string { + return "repo_ref" +} + +const AIContextPayloadTypeRepoRef capabilities2.AIContextPayloadType = "other" + +func (RepoRef) GetType() capabilities2.AIContextPayloadType { + return AIContextPayloadTypeRepoRef +} + +type ChatRequest struct { + Prompt string `json:"prompt"` + ConversationID string `json:"conversation_id"` + ConversationRaw string `json:"conversation_raw"` + Context []capabilities2.AIContext `json:"context"` + Capabilities []capabilities2.CapabilityReference `json:"capabilities"` +} + +func CallAIFoundation(ctx context.Context, cr *capabilities.Registry, + req *ChatRequest) (*capabilitiesctrl.RunCapabilitiesRequest, error) { + url := "http://localhost:8000/chat/gitness" + + jsonData, err := json.Marshal(req) + if err != nil { + return nil, err + } + newReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, err + } + newReq.Header.Add("Authorization", "Bearer "+os.Getenv(AIFoundationServiceToken)) + + client := http.DefaultClient + resp, err := client.Do(newReq) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + in, err := capabilitieshandler.UnmarshalRunCapabilitiesRequest(cr, data) + if err != nil { + return nil, err + } + + return in, nil +} diff --git a/go.mod b/go.mod index 672f049eea..a089787c29 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/drone-runners/drone-runner-docker v1.8.4-0.20240815103043-c6c3a3e33ce3 github.com/drone/drone-go v1.7.1 github.com/drone/drone-yaml v1.2.3 - github.com/drone/funcmap v0.0.0-20240227160611-7e19e9cd5a1c - github.com/drone/go-convert v0.0.0-20240307072510-6bd371c65e61 + github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d + github.com/drone/go-convert v0.0.0-20230919093251-7104c3bcc635 github.com/drone/go-generate v0.0.0-20230920014042-6085ee5c9522 github.com/drone/go-scm v1.38.4 github.com/drone/runner-go v1.12.0 @@ -77,7 +77,6 @@ require ( cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v1.1.12 // indirect dario.cat/mergo v1.0.0 // indirect - github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d // indirect github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BobuSumisu/aho-corasick v1.0.3 // indirect @@ -94,7 +93,6 @@ require ( github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/drone/envsubst v1.0.3 // indirect - github.com/drone/signal v1.0.0 // indirect github.com/fatih/semgroup v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -161,30 +159,31 @@ require ( ) require ( - cloud.google.com/go/profiler v0.4.1 - github.com/Microsoft/go-winio v0.6.2 // indirect + cloud.google.com/go/profiler v0.3.1 + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect + github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/djherbis/buffer v1.2.0 github.com/djherbis/nio/v3 v3.0.1 - github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect github.com/google/subcommands v1.2.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/swaggest/jsonschema-go v0.3.72 - github.com/swaggest/refl v1.3.0 // indirect + github.com/swaggest/jsonschema-go v0.3.40 + github.com/swaggest/refl v1.1.0 // indirect github.com/vearutop/statigz v1.4.0 // indirect - github.com/yuin/goldmark v1.7.4 + github.com/yuin/goldmark v1.4.13 golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/tools v0.23.0 // indirect - google.golang.org/genproto v0.0.0-20240723171418-e6d459c13d2a // indirect + google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index ca35baeecf..1d3ff140ca 100644 --- a/go.sum +++ b/go.sum @@ -10,10 +10,10 @@ cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJ cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw= cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg= -cloud.google.com/go/longrunning v0.5.10 h1:eB/BniENNRKhjz/xgiillrdcH3G74TGSl3BXinGlI7E= -cloud.google.com/go/longrunning v0.5.10/go.mod h1:tljz5guTr5oc/qhlUjBlk7UAIFMOGuPNxkNDZXlLics= -cloud.google.com/go/profiler v0.4.1 h1:Q7+lOvikTGMJ/IAWocpYYGit4SIIoILmVZfEEWTORSY= -cloud.google.com/go/profiler v0.4.1/go.mod h1:LBrtEX6nbvhv1w/e5CPZmX9ajGG9BGLtGbv56Tg4SHs= +cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k= +cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c= +cloud.google.com/go/profiler v0.3.1 h1:b5got9Be9Ia0HVvyt7PavWxXEht15B9lWnigdvHtxOc= +cloud.google.com/go/profiler v0.3.1/go.mod h1:GsG14VnmcMFQ9b+kq71wh3EKMZr3WRMgLzNiFRpW7tE= cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -21,7 +21,6 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= docker.io/go-docker v1.0.0/go.mod h1:7tiAn5a0LFmjbPDbyTPOaTTOuG1ZRNXdPA6RvKY+fpY= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d h1:j6oB/WPCigdOkxtuPl1VSIiLpy7Mdsu6phQffbF19Ng= github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d/go.mod h1:3cARGAK9CfW3HoxCy1a0G4TKrdiKke8ftOMEOHyySYs= github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= @@ -35,8 +34,8 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0 github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -48,8 +47,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= +github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -79,8 +78,8 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= -github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/dev v0.2.32 h1:DRZtloaoH1Igky3zphaUHV9+SLIV2H3lsf78JsJHFg0= +github.com/bool64/dev v0.2.32/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= github.com/buildkite/yaml v2.1.0+incompatible h1:xirI+ql5GzfikVNDmt+yeiXpf/v1Gt03qXTtT5WXdr8= @@ -144,8 +143,6 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/drone-runners/drone-runner-docker v1.8.4-0.20240725142717-515d467f7b29 h1:7k3uDEBK1FcVDb8qFF7P2bWyimsrdcnzs4ZfX02WqqI= -github.com/drone-runners/drone-runner-docker v1.8.4-0.20240725142717-515d467f7b29/go.mod h1:kkQh3P4hJp82vjLTju1RkMvnbY+7cImwaoRH3lExnWA= github.com/drone-runners/drone-runner-docker v1.8.4-0.20240815103043-c6c3a3e33ce3 h1:NnP4ingWdiSNvY5NRit3jrd+kS6j+9Lg2EWfGQqXzTQ= github.com/drone-runners/drone-runner-docker v1.8.4-0.20240815103043-c6c3a3e33ce3/go.mod h1:kkQh3P4hJp82vjLTju1RkMvnbY+7cImwaoRH3lExnWA= github.com/drone/drone-go v1.7.1 h1:ZX+3Rs8YHUSUQ5mkuMLmm1zr1ttiiE2YGNxF3AnyDKw= @@ -156,17 +153,16 @@ github.com/drone/drone-yaml v1.2.3/go.mod h1:QsqliFK8nG04AHFN9tTn9XJomRBQHD4wcej github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/drone/funcmap v0.0.0-20240227160611-7e19e9cd5a1c h1:R4T5Tv7YUrKmw+tBPDvdaz5g7+EElGNIZKY0MWRI3yU= -github.com/drone/funcmap v0.0.0-20240227160611-7e19e9cd5a1c/go.mod h1:nDRkX7PHq+p39AD5/usv3KZMerxZTYU/9rfLS5IDspU= -github.com/drone/go-convert v0.0.0-20240307072510-6bd371c65e61 h1:1mhmsVKAjApVVD0nmyW62SLD8XTc2h/7Hhll7CtOvzY= -github.com/drone/go-convert v0.0.0-20240307072510-6bd371c65e61/go.mod h1:PyCDcuAhGF6W0VJ6qMmlM47dsSyGv/zDiMqeJxMFuGM= +github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d h1:/IO7UVVu191Jc0DajV4cDVoO+91cuppvgxg2MZl+AXI= +github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d/go.mod h1:Hph0/pT6ZxbujnE1Z6/08p5I0XXuOsppqF6NQlGOK0E= +github.com/drone/go-convert v0.0.0-20230919093251-7104c3bcc635 h1:qQX+U2iEm4X2FcmBzxZwZgz8gLpUTa6lBB1vBBCV9Oo= +github.com/drone/go-convert v0.0.0-20230919093251-7104c3bcc635/go.mod h1:PyCDcuAhGF6W0VJ6qMmlM47dsSyGv/zDiMqeJxMFuGM= github.com/drone/go-generate v0.0.0-20230920014042-6085ee5c9522 h1:i3EfRpr/eYifK9w0ninT3xHAthkS4NTQjLX0/zDIsy4= github.com/drone/go-generate v0.0.0-20230920014042-6085ee5c9522/go.mod h1:eTfy716efMJgVvk/ZkRvitaXY2UuytfqDjxclFMeLdQ= github.com/drone/go-scm v1.38.4 h1:KW+znh2tg3tJwbiFfzhjZQ2gbyasJ213V7hZ00QaVpc= github.com/drone/go-scm v1.38.4/go.mod h1:DFIJJjhMj0TSXPz+0ni4nyZ9gtTtC40Vh/TGRugtyWw= github.com/drone/runner-go v1.12.0 h1:zUjDj9ylsJ4n4Mvy4znddq/Z4EBzcUXzTltpzokKtgs= github.com/drone/runner-go v1.12.0/go.mod h1:vu4pPPYDoeN6vdYQAY01GGGsAIW4aLganJNaa8Fx8zE= -github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc= github.com/drone/spec v0.0.0-20230920145636-3827abdce961 h1:aUWrLS2ghyxIpDICpZOV50V1x7JLM3U80UQDQxMKT54= github.com/drone/spec v0.0.0-20230920145636-3827abdce961/go.mod h1:KyQZA9qwuscbbM7yTrtZg25Wammoc5GKwaRem8kDA5k= @@ -285,8 +281,8 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8 h1:ssNFCCVmib/GQSzx3uCWyfMgOamLGWuGqlMS77Y1m3Y= -github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c h1:lvddKcYTQ545ADhBujtIJmqQrZBDsGo7XIMbAQe/sNY= +github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= @@ -358,8 +354,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= -github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA= +github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -691,14 +687,14 @@ github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08Yu github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= -github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= -github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= -github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw= +github.com/swaggest/assertjson v1.7.0/go.mod h1:vxMJMehbSVJd+dDWFCKv3QRZKNTpy/ktZKTz9LOEDng= +github.com/swaggest/jsonschema-go v0.3.40 h1:9EqQ9RvtdW69xfYODmyEKWOSZ12x5eiK+wGD2EVh/L4= +github.com/swaggest/jsonschema-go v0.3.40/go.mod h1:ipIOmoFP64QyRUgyPyU/P9tayq2m2TlvUhyZHrhe3S4= github.com/swaggest/openapi-go v0.2.23 h1:DYUezSTyw180z1bL51wUnalYYbTMwHBjp1Itvji8/rs= github.com/swaggest/openapi-go v0.2.23/go.mod h1:T1Koc6EAFAvnCI1MUqOOPDniqGzZy6dOiHtA/j54k14= -github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= -github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc= +github.com/swaggest/refl v1.1.0/go.mod h1:g3Qa6ki0A/L2yxiuUpT+cuBURuRaltF5SDQpg1kMZSY= github.com/swaggest/swgui v1.8.1 h1:OLcigpoelY0spbpvp6WvBt0I1z+E9egMQlUeEKya+zU= github.com/swaggest/swgui v1.8.1/go.mod h1:YBaAVAwS3ndfvdtW8A4yWDJpge+W57y+8kW+f/DqZtU= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -716,9 +712,8 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= -github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zricethezav/gitleaks/v8 v8.18.5-0.20240614204812-26f34692fac6 h1:UL8vBvxILAVsruyxIGMskACYzOk57nR8aq6dpZLR3KQ= github.com/zricethezav/gitleaks/v8 v8.18.5-0.20240614204812-26f34692fac6/go.mod h1:3EFYK+ZNDHPNQinyZTVGHG7/sFsApEZ9DrCGA1AP63M= @@ -954,8 +949,8 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240723171418-e6d459c13d2a h1:hPbLwHFm59QoSKUT0uGaL19YN4U9W5lY4+iNXlUBNj0= -google.golang.org/genproto v0.0.0-20240723171418-e6d459c13d2a/go.mod h1:+7gIV7FP6jBo5hiY2lsWA//NkNORQVj0J1Isc/4HzR4= +google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg= +google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= google.golang.org/genproto/googleapis/api v0.0.0-20240723171418-e6d459c13d2a h1:YIa/rzVqMEokBkPtydCkx1VLmv3An1Uw7w1P1m6EhOY= google.golang.org/genproto/googleapis/api v0.0.0-20240723171418-e6d459c13d2a/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys= google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a h1:hqK4+jJZXCU4pW7jsAdGOVFIfLHQeV7LaizZKnZ84HI= @@ -1027,7 +1022,6 @@ k8s.io/apimachinery v0.0.0-20181201231028-18a5ff3097b4/go.mod h1:ccL7Eh7zubPUSh9 k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/types/capabilities/capabilities.go b/types/capabilities/capabilities.go new file mode 100644 index 0000000000..ee74ec37dc --- /dev/null +++ b/types/capabilities/capabilities.go @@ -0,0 +1,61 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package capabilities + +import "context" + +type AIContextPayload interface { + GetType() AIContextPayloadType + GetName() string +} + +type AIContextPayloadType string +type AIContext struct { + Type AIContextPayloadType `json:"type"` + Payload AIContextPayload `json:"payload"` + Name string `json:"name"` +} + +type CapabilityReference struct { + Type Type `json:"type"` + Version Version `json:"version"` +} + +type Type string +type Version string + +type Capabilities struct { + Capabilities []Capability `json:"capabilities"` +} + +type Capability struct { + Type Type `json:"type"` + NewInput func() Input `json:"-"` + Logic Logic `json:"-"` + Version Version `json:"version"` + ReturnToUser bool `json:"return_to_user"` +} + +type Logic func(ctx context.Context, input Input) (Output, error) + +type Input interface { + IsCapabilityInput() +} + +type Output interface { + IsCapabilityOutput() + GetType() AIContextPayloadType + GetName() string +} diff --git a/types/enum/ci_status.go b/types/enum/ci_status.go index 8c41dfbc18..40cd7581c7 100644 --- a/types/enum/ci_status.go +++ b/types/enum/ci_status.go @@ -36,6 +36,21 @@ const ( CIStatusError CIStatus = "error" ) +// Enum returns all possible CIStatus values. +func (CIStatus) Enum() []interface{} { + return toInterfaceSlice(ciStatuses) +} + +// Sanitize validates and returns a sanitized CIStatus value. +func (status CIStatus) Sanitize() (CIStatus, bool) { + return Sanitize(status, GetAllCIStatuses) +} + +// GetAllCIStatuses returns all possible CIStatus values and a default value. +func GetAllCIStatuses() ([]CIStatus, CIStatus) { + return ciStatuses, CIStatusPending +} + func (status CIStatus) ConvertToCheckStatus() CheckStatus { if status == CIStatusPending || status == CIStatusWaitingOnDeps { return CheckStatusPending @@ -87,3 +102,17 @@ func (status CIStatus) IsFailed() bool { status == CIStatusKilled || status == CIStatusError } + +// List of all CIStatus values. +var ciStatuses = sortEnum([]CIStatus{ + CIStatusSkipped, + CIStatusBlocked, + CIStatusDeclined, + CIStatusWaitingOnDeps, + CIStatusPending, + CIStatusRunning, + CIStatusSuccess, + CIStatusFailure, + CIStatusKilled, + CIStatusError, +}) diff --git a/types/enum/trigger_actions.go b/types/enum/trigger_actions.go index 7c330c78d3..0358ebcdde 100644 --- a/types/enum/trigger_actions.go +++ b/types/enum/trigger_actions.go @@ -37,9 +37,9 @@ const ( // TriggerActionPullReqBranchUpdated gets triggered when a pull request source branch gets updated. TriggerActionPullReqBranchUpdated TriggerAction = "pullreq_branch_updated" // TriggerActionPullReqClosed gets triggered when a pull request is closed. - TriggerActionPullReqClosed = "pullreq_closed" + TriggerActionPullReqClosed TriggerAction = "pullreq_closed" // TriggerActionPullReqMerged gets triggered when a pull request is merged. - TriggerActionPullReqMerged = "pullreq_merged" + TriggerActionPullReqMerged TriggerAction = "pullreq_merged" ) func (TriggerAction) Enum() []interface{} { return toInterfaceSlice(triggerActions) } diff --git a/types/enum/trigger_events.go b/types/enum/trigger_events.go index 370d08dc62..d329815c2e 100644 --- a/types/enum/trigger_events.go +++ b/types/enum/trigger_events.go @@ -19,9 +19,33 @@ type TriggerEvent string // Hook event constants. const ( - TriggerEventCron = "cron" - TriggerEventManual = "manual" - TriggerEventPush = "push" - TriggerEventPullRequest = "pull_request" - TriggerEventTag = "tag" + TriggerEventCron TriggerEvent = "cron" + TriggerEventManual TriggerEvent = "manual" + TriggerEventPush TriggerEvent = "push" + TriggerEventPullRequest TriggerEvent = "pull_request" + TriggerEventTag TriggerEvent = "tag" ) + +// Enum returns all possible TriggerEvent values. +func (TriggerEvent) Enum() []interface{} { + return toInterfaceSlice(triggerEvents) +} + +// Sanitize validates and returns a sanitized TriggerEvent value. +func (event TriggerEvent) Sanitize() (TriggerEvent, bool) { + return Sanitize(event, GetAllTriggerEvents) +} + +// GetAllTriggerEvents returns all possible TriggerEvent values and a default value. +func GetAllTriggerEvents() ([]TriggerEvent, TriggerEvent) { + return triggerEvents, TriggerEventManual +} + +// List of all TriggerEvent values. +var triggerEvents = sortEnum([]TriggerEvent{ + TriggerEventCron, + TriggerEventManual, + TriggerEventPush, + TriggerEventPullRequest, + TriggerEventTag, +}) diff --git a/types/execution.go b/types/execution.go index ee8fe77630..320954766e 100644 --- a/types/execution.go +++ b/types/execution.go @@ -18,41 +18,41 @@ import "github.com/harness/gitness/types/enum" // Execution represents an instance of a pipeline execution. type Execution struct { - ID int64 `json:"-"` - PipelineID int64 `json:"pipeline_id"` - CreatedBy int64 `json:"created_by"` - RepoID int64 `json:"repo_id"` - Trigger string `json:"trigger,omitempty"` - Number int64 `json:"number"` - Parent int64 `json:"parent,omitempty"` - Status enum.CIStatus `json:"status"` - Error string `json:"error,omitempty"` - Event string `json:"event,omitempty"` - Action string `json:"action,omitempty"` - Link string `json:"link,omitempty"` - Timestamp int64 `json:"timestamp,omitempty"` - Title string `json:"title,omitempty"` - Message string `json:"message,omitempty"` - Before string `json:"before,omitempty"` - After string `json:"after,omitempty"` - Ref string `json:"ref,omitempty"` - Fork string `json:"source_repo,omitempty"` - Source string `json:"source,omitempty"` - Target string `json:"target,omitempty"` - Author string `json:"author_login,omitempty"` - AuthorName string `json:"author_name,omitempty"` - AuthorEmail string `json:"author_email,omitempty"` - AuthorAvatar string `json:"author_avatar,omitempty"` - Sender string `json:"sender,omitempty"` - Params map[string]string `json:"params,omitempty"` - Cron string `json:"cron,omitempty"` - Deploy string `json:"deploy_to,omitempty"` - DeployID int64 `json:"deploy_id,omitempty"` - Debug bool `json:"debug,omitempty"` - Started int64 `json:"started,omitempty"` - Finished int64 `json:"finished,omitempty"` - Created int64 `json:"created"` - Updated int64 `json:"updated"` - Version int64 `json:"-"` - Stages []*Stage `json:"stages,omitempty"` + ID int64 `json:"-"` + PipelineID int64 `json:"pipeline_id"` + CreatedBy int64 `json:"created_by"` + RepoID int64 `json:"repo_id"` + Trigger string `json:"trigger,omitempty"` + Number int64 `json:"number"` + Parent int64 `json:"parent,omitempty"` + Status enum.CIStatus `json:"status"` + Error string `json:"error,omitempty"` + Event enum.TriggerEvent `json:"event,omitempty"` + Action enum.TriggerAction `json:"action,omitempty"` + Link string `json:"link,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` + Title string `json:"title,omitempty"` + Message string `json:"message,omitempty"` + Before string `json:"before,omitempty"` + After string `json:"after,omitempty"` + Ref string `json:"ref,omitempty"` + Fork string `json:"source_repo,omitempty"` + Source string `json:"source,omitempty"` + Target string `json:"target,omitempty"` + Author string `json:"author_login,omitempty"` + AuthorName string `json:"author_name,omitempty"` + AuthorEmail string `json:"author_email,omitempty"` + AuthorAvatar string `json:"author_avatar,omitempty"` + Sender string `json:"sender,omitempty"` + Params map[string]string `json:"params,omitempty"` + Cron string `json:"cron,omitempty"` + Deploy string `json:"deploy_to,omitempty"` + DeployID int64 `json:"deploy_id,omitempty"` + Debug bool `json:"debug,omitempty"` + Started int64 `json:"started,omitempty"` + Finished int64 `json:"finished,omitempty"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` + Version int64 `json:"-"` + Stages []*Stage `json:"stages,omitempty"` } diff --git a/types/execution_analysis.go b/types/execution_analysis.go new file mode 100644 index 0000000000..51254671da --- /dev/null +++ b/types/execution_analysis.go @@ -0,0 +1,25 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type AnalyseExecutionInput struct { + RepoRef string `json:"repo_ref"` + PipelineIdentifier string `json:"pipeline_identifier"` + ExecutionNum int64 `json:"execution_number"` +} + +type AnalyseExecutionOutput struct { + Yaml string `json:"yaml"` +} diff --git a/types/pipeline.go b/types/pipeline.go index b15a8e6275..a177c19fb7 100644 --- a/types/pipeline.go +++ b/types/pipeline.go @@ -32,6 +32,9 @@ type Pipeline struct { Execution *Execution `db:"-" json:"execution,omitempty"` Updated int64 `db:"pipeline_updated" json:"updated"` Version int64 `db:"pipeline_version" json:"-"` + + // Repo specific information not stored with pipelines + RepoUID string `db:"-" json:"repo_uid,omitempty"` } // TODO [CODE-1363]: remove after identifier migration. diff --git a/types/suggestions.go b/types/suggestions.go new file mode 100644 index 0000000000..775bc2ffe0 --- /dev/null +++ b/types/suggestions.go @@ -0,0 +1,50 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type PipelineSuggestionsRequest struct { + RepoRef string + Pipeline string +} + +type PipelineGenerateRequest struct { + Prompt string + RepoRef string +} + +type PipelineUpdateRequest struct { + Prompt string + RepoRef string + Pipeline string +} + +type PipelineGenerateResponse struct { + YAML string +} + +type PipelineUpdateResponse struct { + YAML string +} + +type Suggestion struct { + ID string + Prompt string + UserSuggestion string + Suggestion string +} + +type PipelineSuggestionsResponse struct { + Suggestions []Suggestion +}