Compare commits

..

2 Commits

Author SHA1 Message Date
Dhruv ec4509cc1f
Update devcontainer.json 2024-10-29 14:24:04 +05:30
Dhruv 0a1d02e3ca
Create devcontainer.json 2024-10-29 13:13:24 +05:30
3428 changed files with 28076 additions and 266679 deletions

View File

@ -1,98 +0,0 @@
# syntax=docker/dockerfile:1
# =============================================================================
# Stage 1: Base tools installation (rarely changes, excellent caching)
# =============================================================================
FROM mcr.microsoft.com/devcontainers/go:1-1.23 as tools
# Install system packages in single layer for better caching
RUN sudo apt update && sudo apt install -y \
nodejs \
lsb-release \
curl \
gpg \
protobuf-compiler \
git-lfs \
&& sudo apt-get clean \
&& sudo rm -rf /var/lib/apt/lists/*
# Install Go tools (these rarely change), separately to avoid memory issues
RUN export GOMAXPROCS=1 && go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.1
RUN export GOMAXPROCS=1 && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0
ENV PATH="${PATH}:$(go env GOPATH)/bin"
# =============================================================================
# Stage 2: External services installation (moderate caching)
# =============================================================================
FROM tools as services
ARG TARGETARCH
# Install redis
RUN curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg && \
sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg && \
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list && \
sudo apt-get update -y && \
sudo apt-get install redis -y && \
sudo apt-get clean && \
sudo rm -rf /var/lib/apt/lists/*
# Install gcloud and kubectl
RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg && \
sudo apt-get update && \
sudo apt-get install -y \
google-cloud-cli \
kubectl \
google-cloud-cli-gke-gcloud-auth-plugin \
&& sudo apt-get clean \
&& sudo rm -rf /var/lib/apt/lists/*
# Install binary tools with architecture support
RUN curl -fsSL https://raw.githubusercontent.com/metalbear-co/mirrord/main/scripts/install.sh | bash
# Install yq with architecture detection
RUN curl -Lo /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_${TARGETARCH} && \
sudo chmod +x /usr/local/bin/yq
# Configure Go to handle private modules
RUN go env -w GOPRIVATE=$(go env GOPRIVATE),git0.harness.io
# =============================================================================
# Stage 3: Code preparation and build (secure - credentials cleaned in same layer)
# =============================================================================
FROM services as builder
ARG BRANCH
# Arg to track commit hash — cache busts only when this changes
ARG COMMIT_SHA
WORKDIR /root
RUN --mount=type=secret,id=harness_code_secret_harness0,env=HARNESS_CODE_SECRET_HARNESS0 \
--mount=type=secret,id=harness_code_user,env=HARNESS_CODE_USER \
--mount=type=secret,id=github_secret,env=GITHUB_SECRET \
--mount=type=secret,id=github_user,env=GITHUB_USER \
echo $COMMIT_SHA > /commit.txt && \
git config --global credential.helper store && \
echo "https://${HARNESS_CODE_USER}:${HARNESS_CODE_SECRET_HARNESS0}@git0.harness.io" >> ~/.git-credentials && \
echo "https://${GITHUB_USER}:${GITHUB_SECRET}@github.com" >> ~/.git-credentials && \
echo "@harness:registry=https://npm.pkg.github.com" > ~/.npmrc && \
echo "//npm.pkg.github.com/:_authToken=${GITHUB_SECRET}" >> ~/.npmrc && \
echo "always-auth=true" >> ~/.npmrc && \
echo "machine git0.harness.io login git password ${HARNESS_CODE_SECRET_HARNESS0}" >> ~/.netrc && \
git clone -b ${BRANCH} https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness.git && \
cd /root/gitness && \
git lfs install && git lfs pull && \
make init && \
make dep && \
make tools && \
make web-build && \
make build && \
rm -f ~/.git-credentials && git config --global --unset credential.helper && \
sed -i 's|//npm.pkg.github.com/:_authToken=.*|//npm.pkg.github.com/:_authToken=xxx|' ~/.npmrc && \
sed -i '/machine git0\.harness\.io/d' ~/.netrc
WORKDIR /root/gitness

View File

@ -1,19 +1,3 @@
{
"image": "harness0.harness.io/oci/gitspaces-image-registry/gitness-base:main-amd64",
"remoteUser": "root",
"forwardPorts": [
"3000"
],
"customizations": {
"harnessGitspaces": {
"connectors": [
{
"type": "DockerRegistry",
"identifier": "org.gitspacesimageregistry"
}
]
}
},
"postCreateCommand": "sudo chmod +x /root/gitness/.devcontainer/postCreate.sh && /root/gitness/.devcontainer/postCreate.sh",
"postStartCommand": "sudo chmod +x /root/gitness/.devcontainer/postStart.sh && /root/gitness/.devcontainer/postStart.sh"
}
"image": "mcr.microsoft.com/devcontainers/go:1-1.22"
}

View File

@ -1,2 +0,0 @@
#!/bin/sh

View File

@ -1,5 +0,0 @@
#!/bin/sh
#sudo service redis-server start
redis-server &

View File

@ -1,68 +0,0 @@
#!/bin/bash
# Copyright 2025 Harness Inc. All rights reserved.
# Use of this source code is governed by the PolyForm Free Trial 1.0.0 license
# that can be found in the licenses directory at the root of this repository, also available at
# https://polyformproject.org/wp-content/uploads/2020/05/PolyForm-Free-Trial-1.0.0.txt.
###################################################
# Purpose
# The purpose of this script is to facilitate auto-tagging of
# Jira tickets with fix-versions. There are two tricky parts to auto-tagging:
#
# 1. Given a change set (PR diff), what constitutes a change to a service?
# 2. Given you've determined a set of files has changed a service, which service was changed?
#
# This script endeavors to answer question number one - which files constitute a change to a service
# Given an input file which is the git diff from a PR
# this script should determine what file changes in the diff
# constitute a material change to a service. At the time
# of this writing, this currently only identifies java and go
# files, and if those files are in the change list, then those
# file names are returned in the output file.
#
# Inputs
# $1 - File containing unique changed files
# $2 - the output file which should ultimately contain file names from the diff that affect a service
#
# See BT-10437 for more information
#
# Called by https://harness0.harness.io/ng/account/l7B_kbSEQD2wjrM7PShm5w/all/orgs/Audit/projects/Engops_Audit/pipelines/PRMergedGithub/pipeline-studio/?storeType=INLINE
# Unlike other scripts for detecting changes, this one doesn't receive a git diff, rather a unique list of files changed by the git update.
#
# Owner: Engops
# Author: Marc Batchelor
###################################################
echo "Arguments: " $*
uniqueFileNamesFile=$1
sourceDiffNames=$2
if [ -z "$uniqueFileNamesFile" ]; then
echo "Missing input PR Difference file."
exit 1
fi
if [ ! -f "$uniqueFileNamesFile" ]; then
echo "Input file $uniqueFileNamesFile does not exist and is required."
exit 2
fi
if [ -z "$sourceDiffNames" ]; then
echo "Missing output file."
exit 3
fi
if [ ! -f "$sourceDiffNames" ]; then
echo "File $sourceDiffNames does not exist and is required."
exit 4
fi
##### Detect git diff file, or processed filenames only
isDiffFile=$(grep -E "^diff --git a\/" "$uniqueFileNamesFile" | wc -l)
if [ $isDiffFile -gt 0 ]; then
echo "Received a diff file... fix it to be a filenames only file"
fileNamesOnlyVar=$(cat "$uniqueFileNamesFile"|grep -E "^diff --git" | sed 's/diff --git a\///' | sed 's/ b\/.*$//' | sort -u)
echo -e "$fileNamesOnlyVar">"$uniqueFileNamesFile"
fi
# Java files (and other files) which end up in jars - these are kept in .../src/main/x/x/x/*
cat "$uniqueFileNamesFile" | grep -E ".*.java$" | grep -v "/test/" > $sourceDiffNames
# go files (without tests)
cat "$uniqueFileNamesFile" | grep -E ".*.go$|.*.mod$" | grep -v "test_" >> $sourceDiffNames
# Other source files
cat "$uniqueFileNamesFile" | grep -E ".*.(Dockerfile|Dockerfile.cov|Dockerfile.dev|bazel|c|cc|conf|css|ejs|eslintrc|gitmodules|go|golang|gradle|gv|graphql|h|html|iml|js|json|less|mod|pipeline|mustache|pl|png|properties|ps1|proto|py|pyc|qbg|repo|rs|sh||sha256|sql|sum|svg|tf|tgz|tmpl|tpl|ts|tsx|xml|yaml|yml)$" >> $sourceDiffNames

View File

@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.23'
go-version: '1.22'
- name: get dependencies
run: |
mkdir -p ./web/dist
@ -44,7 +44,7 @@ jobs:
uses: golangci/golangci-lint-action@v3
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: v1.64.5
version: v1.54
# Optional: working directory, useful for monorepos
# working-directory: somedir

5
.gitignore vendored
View File

@ -24,14 +24,9 @@ dist
.yalc
yalc.lock
node_modules
.cursor
# ignore any executables we build
/gitness
/registry/logs/*
/distribution-spec
/registry/distribution-spec
/app/store/database/test.db
# adding support for .http files
http-client.private.env.json

View File

@ -1,485 +1,499 @@
version: "2"
linters:
default: none
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- contextcheck
- copyloopvar
- durationcheck
- errcheck
- errname
- errorlint
- exhaustive
- forbidigo
- goconst
- gocritic
- godot
- goheader
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- govet
- ineffassign
- lll
- makezero
- misspell
- nakedret
- nestif
- nilerr
- nilnil
- noctx
- nosprintfhostport
- predeclared
- promlinter
- reassign
- revive
- rowserrcheck
- sqlclosecheck
- staticcheck
- tagliatelle
- tparallel
- unconvert
- unparam
- unused
- usestdlibvars
- wastedassign
- whitespace
settings:
revive:
## Golden config for golangci-lint v1.49.0
run:
# Timeout for analysis, e.g. 30s, 5m.
# Default: 1m
timeout: 3m
# This file contains only configs which differ from defaults.
# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
linters-settings:
cyclop:
# The maximal code complexity to report.
# Default: 10
max-complexity: 30
# The maximal average package complexity.
# If it's higher than 0.0 (float) the check is enabled
# Default: 0.0
package-average: 10.0
errcheck:
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
# Such cases aren't reported by default.
# Default: false
check-type-assertions: true
funlen:
# Checks the number of lines in a function.
# If lower than 0, disable the check.
# Default: 60
lines: 100
# Checks the number of statements in a function.
# If lower than 0, disable the check.
# Default: 40
statements: 50
gocritic:
# Settings passed to gocritic.
# The settings key is the name of a supported gocritic checker.
# The list of supported checkers can be find in https://go-critic.github.io/overview.
settings:
captLocal:
# Whether to restrict checker to params only.
# Default: true
paramsOnly: false
underef:
# Whether to skip (*x).method() calls where x is a pointer receiver.
# Default: true
skipRecvDeref: false
gomnd:
# List of function patterns to exclude from analysis.
# Values always ignored: `time.Date`
# Default: []
ignored-functions:
- os.Chmod
- os.Mkdir
- os.MkdirAll
- os.OpenFile
- os.WriteFile
- prometheus.ExponentialBuckets
- prometheus.ExponentialBucketsRange
- prometheus.LinearBuckets
- strconv.FormatFloat
- strconv.FormatInt
- strconv.FormatUint
- strconv.ParseFloat
- strconv.ParseInt
- strconv.ParseUint
gomodguard:
blocked:
# List of blocked modules.
# Default: []
modules:
- github.com/golang/protobuf:
recommendations:
- google.golang.org/protobuf
reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules"
- github.com/satori/go.uuid:
recommendations:
- github.com/google/uuid
reason: "satori's package is not maintained"
- github.com/gofrs/uuid:
recommendations:
- github.com/google/uuid
reason: "see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw"
govet:
# Enable all analyzers.
# Default: false
enable-all: true
# Disable analyzers by name.
# Run `go tool vet help` to see all analyzers.
# Default: []
disable:
- fieldalignment # too strict
# Settings per analyzer.
settings:
shadow:
# Whether to be strict about shadowing; can be noisy.
# Default: false
strict: true
nakedret:
# Make an issue if func has more lines of code than this setting, and it has naked returns.
# Default: 30
max-func-lines: 30
rowserrcheck:
# database/sql is always checked
# Default: []
packages:
- github.com/jmoiron/sqlx
tenv:
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
# Default: false
all: true
goheader:
# The template use for checking.
# Default: ""
template: |-
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.
# As alternative of directive 'template', you may put the path to file with the template source.
# Useful if you need to load the template from a specific file.
# Default: ""
# template-path: /path/to/my/template.tmpl
gci:
# DEPRECATED: use `sections` and `prefix(github.com/org/project)` instead.
# local-prefixes: github.com/harness/gitness
# Section configuration to compare against.
# Section names are case-insensitive and may contain parameters in ().
# The default order of sections is `standard > default > custom > blank > dot`,
# If `custom-order` is `true`, it follows the order of `sections` option.
# Default: ["standard", "default"]
sections:
- standard # Standard section: captures all standard packages.
- prefix(github.com/harness/gitness) # Custom section: groups all imports with the specified Prefix.
- default # Default section: contains all imports that could not be matched to another section type.
- blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled.
- dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled.
# Skip generated files.
# Default: true
skip-generated: false
# Enable custom order of sections.
# If `true`, make the section order the same as the order of `sections`.
# Default: false
custom-order: true
tagliatelle:
# Check the struck tag name case.
case:
rules:
- name: var-naming
severity: warning
disabled: true
errcheck:
check-type-assertions: true
gocritic:
settings:
captLocal:
paramsOnly: false
underef:
skipRecvDeref: false
goheader:
template: |-
Copyright 2023 Harness, Inc.
# Any struct tag type can be used.
# Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
json: snake
db: snake
yaml: snake
xml: snake
bson: snake
avro: snake
mapstructure: snake
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
linters:
disable-all: true
enable:
## enabled by default
- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases
- gosimple # specializes in simplifying a code
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- ineffassign # detects when assignments to existing variables are not used
- staticcheck # is a go vet on steroids, applying a ton of static analysis checks
- typecheck # like the front-end of a Go compiler, parses and type-checks Go code
- unused # checks for unused constants, variables, functions and types
## disabled by default
- asasalint # checks for pass []any as any in variadic func(...any)
- asciicheck # checks that your code does not contain non-ASCII identifiers
- bidichk # checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
#- contextcheck # checks the function whether use a non-inherited context # TODO: enable after golangci-lint uses https://github.com/sylvia7788/contextcheck/releases/tag/v1.0.7
- cyclop # checks function and package cyclomatic complexity
# - dupl # tool for code clone detection
- durationcheck # checks for two durations multiplied together
- errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
- execinquery # checks query string in Query function which reads your Go src files and warning it finds
- exhaustive # checks exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- forbidigo # forbids identifiers
#- funlen # tool for detection of long functions
#- gochecknoglobals # checks that no global variables exist
#- gochecknoinits # checks that no init functions are present in Go code
- gocognit # computes and checks the cognitive complexity of functions
- goconst # finds repeated strings that could be replaced by a constant
- gocritic # provides diagnostics that check for bugs, performance and style issues
- gocyclo # computes and checks the cyclomatic complexity of functions
- godot # checks if comments end in a period
- goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt
- gomnd # detects magic numbers
- gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod
- gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations
- goprintffuncname # checks that printf-like functions are named with f at the end
- gosec # inspects source code for security problems
- lll # reports long lines
- makezero # finds slice declarations with non-zero initial length
- nakedret # finds naked returns in functions greater than a specified function length
- nestif # reports deeply nested if statements
- nilerr # finds the code that returns nil even if it checks that the error is not nil
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
- noctx # finds sending http request without context.Context
# - nolintlint # reports ill-formed or insufficient nolint directives
# - nonamedreturns # reports all named returns
- nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL
- predeclared # finds code that shadows one of Go's predeclared identifiers
- promlinter # checks Prometheus metrics naming via promlint
- reassign # checks that package variables are not reassigned
- revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint
- rowserrcheck # checks whether Err of rows is checked successfully
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
- stylecheck # is a replacement for golint
- tagliatelle # checks the struct tags
- tenv # detects using os.Setenv instead of t.Setenv since Go1.17
#- testpackage # makes you use a separate _test package
- tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes
- unconvert # removes unnecessary type conversions
- unparam # reports unused function parameters
- usestdlibvars # detects the possibility to use variables/constants from the Go standard library
- wastedassign # finds wasted assignment statements
- whitespace # detects leading and trailing whitespace
## you may want to enable
#- decorder # checks declaration order and count of types, constants, variables and functions
#- exhaustruct # checks if all structure fields are initialized
- gci # controls golang package import order and makes it always deterministic
#- godox # detects FIXME, TODO and other comment keywords
- goheader # checks is file header matches to pattern
#- interfacebloat # checks the number of methods inside an interface
#- ireturn # accept interfaces, return concrete types
#- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated
#- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
#- wrapcheck # checks that errors returned from external packages are wrapped
## disabled
#- containedctx # detects struct contained context.Context field
#- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages
#- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
#- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted
#- forcetypeassert # [replaced by errcheck] finds forced type assertions
#- goerr113 # [too strict] checks the errors handling expressions
#- gofmt # [replaced by goimports] checks whether code was gofmt-ed
#- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed
#- grouper # analyzes expression groups
#- importas # enforces consistent import aliases
#- logrlint # [owner archived repository] checks logr arguments
#- maintidx # measures the maintainability index of each function
- misspell # [useless] finds commonly misspelled English words in comments
#- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity
#- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test
#- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers
#- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines
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.
gomodguard:
blocked:
modules:
- github.com/golang/protobuf:
recommendations:
- google.golang.org/protobuf
reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules
- github.com/satori/go.uuid:
recommendations:
- github.com/google/uuid
reason: satori's package is not maintained
- github.com/gofrs/uuid:
recommendations:
- github.com/google/uuid
reason: 'see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw'
govet:
disable:
- fieldalignment
enable-all: true
settings:
shadow:
strict: true
nakedret:
max-func-lines: 30
rowserrcheck:
packages:
- github.com/jmoiron/sqlx
staticcheck:
checks:
- all
- -SA1019
- -QF1008
tagliatelle:
case:
rules:
avro: snake
bson: snake
db: snake
json: snake
mapstructure: snake
xml: snake
yaml: snake
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- govet
text: 'shadow: declaration of "(err|ctx|ok)" shadows declaration at'
- linters:
- lll
source: ^//\s*go:generate\s
- linters:
- gomoddirectives
text: 'local replacement are not allowed: github.com/harness/gitness'
- linters:
- gomoddirectives
text: 'replacement are not allowed: github.com/docker/docker'
- linters:
- godot
source: (noinspection|TODO)
- linters:
- gocritic
source: //noinspection
- linters:
- errorlint
source: ^\s+if _, ok := err\.\([^.]+\.InternalError\); ok {
- linters:
- forbidigo
path: ^cli/
- linters:
- revive
- staticcheck
- tagliatelle
path: ^registry/app/manifest/.*
- linters:
- errorlint
path: ^registry/app/dist_temp/.*
- linters:
- gocritic
path: ^registry/app/driver/filesystem/.*
- linters:
- gocognit
- gosec
- nestif
path: ^registry/app/driver/s3-aws/.*
- linters:
- goheader
path: ^registry/app/remote/clients/registry/interceptor/interceptor.go
- linters:
- goheader
path: ^registry/app/common/http/modifier/modifier.go
- linters:
- goheader
path: ^registry/app/driver/fileinfo.go
- linters:
- goheader
path: ^registry/app/driver/storagedriver.go
- linters:
- goheader
path: ^registry/app/driver/walk.go
- linters:
- goheader
path: ^registry/app/dist_temp/challenge/addr.go
- linters:
- goheader
path: ^registry/app/dist_temp/challenge/authchallenge.go
- linters:
- goheader
path: ^registry/app/dist_temp/challenge/authchallenge_test.go
- linters:
- goheader
path: ^registry/app/dist_temp/requestutil/util.go
- linters:
- goheader
path: ^registry/app/dist_temp/requestutil/util_test.go
- linters:
- goheader
path: ^registry/app/pkg/commons/zipreader/*
- linters:
- goheader
path: ^registry/app/manifest/descriptor.go
- linters:
- goheader
path: ^registry/app/manifest/doc.go
- linters:
- goheader
path: ^registry/app/manifest/errors.go
- linters:
- goheader
path: ^registry/app/manifest/manifests.go
- linters:
- goheader
path: ^registry/app/manifest/versioned.go
- linters:
- goheader
path: ^registry/app/common/lib/authorizer.go
- linters:
- goheader
path: ^registry/app/common/lib/link.go
- linters:
- goheader
path: ^registry/app/common/http/tls.go
- linters:
- goheader
path: ^registry/app/common/http/transport.go
- linters:
- goheader
path: ^registry/app/common/http/transport_test.go
- linters:
- goheader
path: ^registry/app/manifest/schema2/manifest.go
- linters:
- goheader
path: ^registry/app/manifest/schema2/manifest_test.go
- linters:
- goheader
path: ^registry/app/manifest/ocischema/index.go
- linters:
- goheader
path: ^registry/app/manifest/ocischema/manifest.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/auth/null/authorizer.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/auth/basic/authorizer.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/auth/basic/authorizer_test.go
- linters:
- goheader
path: ^registry/app/common/lib/errors/const.go
- linters:
- goheader
path: ^registry/app/common/lib/errors/errors.go
- linters:
- goheader
path: ^registry/app/common/lib/errors/stack.go
- linters:
- goheader
path: ^registry/app/common/lib/errors/stack_test.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/auth/bearer/authorizer.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/auth/bearer/cache.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/auth/bearer/scope.go
- linters:
- goheader
path: ^registry/app/manifest/manifestlist/manifestlist.go
- linters:
- goheader
path: ^registry/app/manifest/manifestlist/manifestlist_test.go
- linters:
- goheader
path: ^registry/app/driver/factory/factory.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/context.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/doc.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/http.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/logger.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/trace.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/util.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/version.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/http_test.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/trace_test.go
- linters:
- goheader
path: ^registry/app/dist_temp/dcontext/version_test.go
- linters:
- goheader
path: ^registry/app/driver/base/base.go
- linters:
- goheader
path: ^registry/app/driver/base/regulator.go
- linters:
- goheader
path: ^registry/app/driver/base/regulator_test.go
- linters:
- goheader
path: ^registry/app/storage/blobs.go
- linters:
- goheader
path: ^registry/app/storage/blobwriter.go
- linters:
- goheader
path: ^registry/app/storage/blobwriter_resumable.go
- linters:
- goheader
path: ^registry/app/storage/errors.go
- linters:
- goheader
path: ^registry/app/storage/filereader.go
- linters:
- goheader
path: ^registry/app/storage/gcstoragelient.go
- linters:
- goheader
path: ^registry/app/storage/io.go
- linters:
- goheader
path: ^registry/app/storage/middleware.go
- linters:
- goheader
path: ^registry/app/storage/ociblobstore.go
- linters:
- goheader
path: ^registry/app/storage/paths.go
- linters:
- goheader
path: ^registry/app/storage/storageservice.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/client.go
- linters:
- goheader
path: ^registry/app/remote/adapter/adapter.go
- linters:
- goheader
path: ^registry/app/remote/clients/registry/auth/authorizer.go
- linters:
- goheader
path: ^registry/app/driver/s3-aws/s3.go
- linters:
- goheader
path: ^registry/app/driver/s3-aws/s3_v2_signer.go
- linters:
- goheader
path: ^registry/app/driver/filesystem/driver.go
- linters:
- goheader
path: ^registry/app/pkg/docker/app.go
- linters:
- goheader
path: ^registry/app/pkg/docker/catalog.go
- linters:
- goheader
path: ^registry/app/pkg/docker/compat.go
- linters:
- goheader
path: ^registry/app/pkg/docker/context.go
- linters:
- goheader
path: ^registry/app/pkg/docker/controller.go
- linters:
- goheader
path: ^registry/app/pkg/docker/local.go
- linters:
- goheader
path: ^registry/app/pkg/docker/manifest_service.go
- linters:
- goheader
path: ^registry/app/pkg/docker/remote.go
- linters:
- goheader
path: ^registry/app/remote/adapter/dockerhub/adapter.go
- linters:
- goheader
path: ^registry/app/remote/adapter/awsecr/adapter.go
- linters:
- goheader
path: ^registry/app/remote/adapter/maven/adapter.go
- linters:
- goheader
path: ^registry/app/remote/adapter/awsecr/auth.go
- linters:
- goheader
path: ^registry/app/remote/adapter/dockerhub/client.go
- linters:
- goheader
path: ^registry/app/remote/adapter/dockerhub/consts.go
- linters:
- goheader
path: ^registry/app/driver/testsuites/testsuites.go
- linters:
- goheader
path: ^registry/app/dist_temp/errcode/errors.go
- linters:
- goheader
path: ^registry/app/dist_temp/errcode/handler.go
- linters:
- goheader
path: ^registry/app/dist_temp/errcode/register.go
- linters:
- goheader
path: ^registry/app/remote/controller/proxy/controller.go
- linters:
- goheader
path: ^registry/app/remote/controller/proxy/inflight.go
- linters:
- goheader
path: ^registry/app/remote/controller/proxy/local.go
- linters:
- goheader
path: ^registry/app/remote/controller/proxy/remote.go
- linters:
- goheader
path: ^registry/app/remote/controller/proxy/inflight_test.go
- linters:
- goheader
path: ^registry/app/remote/adapter/native/adapter.go
- linters:
- gosec
path: ^registry/app/storage/blobStore.go
- linters:
- lll
- tagliatelle
path: ^registry/app/metadata/nuget/metadata.go
- linters:
- errcheck
- gocritic
- godot
- goheader
- lll
path: ^registry/app/api/controller/mocks/
paths:
- third_party$
- builtin$
- examples$
issues:
max-same-issues: 10
formatters:
enable:
- gci
- goimports
settings:
gci:
sections:
- standard
- prefix(github.com/harness/gitness)
- default
- blank
- dot
custom-order: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 50
exclude-rules:
- text: 'shadow: declaration of "(err|ctx)" shadows declaration at'
linters: [ govet ]
- source: "^//\\s*go:generate\\s"
linters: [ lll ]
- text: 'local replacement are not allowed: github.com/harness/gitness'
linters: [ gomoddirectives ]
- text: 'replacement are not allowed: github.com/docker/docker'
linters: [ gomoddirectives ]
- source: "(noinspection|TODO)"
linters: [ godot ]
- source: "//noinspection"
linters: [ gocritic ]
- source: "^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {"
linters: [ errorlint ]
- path: "^cli/"
linters: [forbidigo]
#Registry Specific
- path: "^registry/app/manifest/.*"
linters: [ tagliatelle, staticcheck, revive ]
- path: "^registry/app/dist_temp/.*"
linters: [ errorlint ]
- path: "^registry/app/driver/filesystem/.*"
linters: [ gocritic ]
- path: "^registry/app/driver/s3-aws/.*"
linters: [ gocognit, gocyclo, gosec, nestif, cyclop]
- path: "^registry/app/remote/clients/registry/interceptor/interceptor.go"
linters: [ goheader ]
- path: "^registry/app/common/http/modifier/modifier.go"
linters: [ goheader ]
- path: "^registry/app/driver/fileinfo.go"
linters: [ goheader ]
- path: "^registry/app/driver/storagedriver.go"
linters: [ goheader ]
- path: "^registry/app/driver/walk.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/challenge/addr.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/challenge/authchallenge.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/challenge/authchallenge_test.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/requestutil/util.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/requestutil/util_test.go"
linters: [ goheader ]
- path: "^registry/app/manifest/descriptor.go"
linters: [ goheader ]
- path: "^registry/app/manifest/doc.go"
linters: [ goheader ]
- path: "^registry/app/manifest/errors.go"
linters: [ goheader ]
- path: "^registry/app/manifest/manifests.go"
linters: [ goheader ]
- path: "^registry/app/manifest/versioned.go"
linters: [ goheader ]
- path: "^registry/app/common/lib/authorizer.go"
linters: [ goheader ]
- path: "^registry/app/common/lib/link.go"
linters: [ goheader ]
- path: "^registry/app/common/http/tls.go"
linters: [ goheader ]
- path: "^registry/app/common/http/transport.go"
linters: [ goheader ]
- path: "^registry/app/common/http/transport_test.go"
linters: [ goheader ]
- path: "^registry/app/manifest/schema2/manifest.go"
linters: [ goheader ]
- path: "^registry/app/manifest/schema2/manifest_test.go"
linters: [ goheader ]
- path: "^registry/app/manifest/ocischema/index.go"
linters: [ goheader ]
- path: "^registry/app/manifest/ocischema/manifest.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/auth/null/authorizer.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/auth/basic/authorizer.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/auth/basic/authorizer_test.go"
linters: [ goheader ]
- path: "^registry/app/common/lib/errors/const.go"
linters: [ goheader ]
- path: "^registry/app/common/lib/errors/errors.go"
linters: [ goheader ]
- path: "^registry/app/common/lib/errors/stack.go"
linters: [ goheader ]
- path: "^registry/app/common/lib/errors/stack_test.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/auth/bearer/authorizer.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/auth/bearer/cache.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/auth/bearer/scope.go"
linters: [ goheader ]
- path: "^registry/app/manifest/manifestlist/manifestlist.go"
linters: [ goheader ]
- path: "^registry/app/manifest/manifestlist/manifestlist_test.go"
linters: [ goheader ]
- path: "^registry/app/driver/factory/factory.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/context.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/doc.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/http.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/logger.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/trace.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/util.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/version.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/http_test.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/trace_test.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/dcontext/version_test.go"
linters: [ goheader ]
- path: "^registry/app/driver/base/base.go"
linters: [ goheader ]
- path: "^registry/app/driver/base/regulator.go"
linters: [ goheader ]
- path: "^registry/app/driver/base/regulator_test.go"
linters: [ goheader ]
- path: "^registry/app/storage/blobs.go"
linters: [ goheader ]
- path: "^registry/app/storage/blobwriter.go"
linters: [ goheader ]
- path: "^registry/app/storage/blobwriter_resumable.go"
linters: [ goheader ]
- path: "^registry/app/storage/errors.go"
linters: [ goheader ]
- path: "^registry/app/storage/filereader.go"
linters: [ goheader ]
- path: "^registry/app/storage/gcstoragelient.go"
linters: [ goheader ]
- path: "^registry/app/storage/io.go"
linters: [ goheader ]
- path: "^registry/app/storage/middleware.go"
linters: [ goheader ]
- path: "^registry/app/storage/ociblobstore.go"
linters: [ goheader ]
- path: "^registry/app/storage/paths.go"
linters: [ goheader ]
- path: "^registry/app/storage/storageservice.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/client.go"
linters: [ goheader ]
- path: "^registry/app/remote/adapter/adapter.go"
linters: [ goheader ]
- path: "^registry/app/remote/clients/registry/auth/authorizer.go"
linters: [ goheader ]
- path: "^registry/app/driver/s3-aws/s3.go"
linters: [ goheader ]
- path: "^registry/app/driver/s3-aws/s3_v2_signer.go"
linters: [ goheader ]
- path: "^registry/app/driver/filesystem/driver.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/app.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/catalog.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/compat.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/context.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/controller.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/local.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/manifest_service.go"
linters: [ goheader ]
- path: "^registry/app/pkg/docker/remote.go"
linters: [ goheader ]
- path: "^registry/app/remote/adapter/dockerhub/adapter.go"
linters: [ goheader ]
- path: "^registry/app/remote/adapter/dockerhub/client.go"
linters: [ goheader ]
- path: "^registry/app/remote/adapter/dockerhub/consts.go"
linters: [ goheader ]
- path: "^registry/app/driver/testsuites/testsuites.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/errcode/errors.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/errcode/handler.go"
linters: [ goheader ]
- path: "^registry/app/dist_temp/errcode/register.go"
linters: [ goheader ]
- path: "^registry/app/remote/controller/proxy/controller.go"
linters: [ goheader ]
- path: "^registry/app/remote/controller/proxy/inflight.go"
linters: [ goheader ]
- path: "^registry/app/remote/controller/proxy/local.go"
linters: [ goheader ]
- path: "^registry/app/remote/controller/proxy/remote.go"
linters: [ goheader ]
- path: "^registry/app/remote/controller/proxy/inflight_test.go"
linters: [ goheader ]
- path: "^registry/app/remote/adapter/native/adapter.go"
linters: [ goheader ]
#Registry Specific ends
- text: "mnd: Magic number: \\d"
linters:
- gomnd
- path: "_test\\.go"
linters:
- bodyclose
- dupl
- funlen
- goconst
- gosec
- noctx
- wrapcheck

View File

@ -7,13 +7,10 @@ GITNESS_METRIC_ENABLED=false
GITNESS_HTTP_HOST=localhost
GITNESS_GITSPACE_ENABLE=true
GITNESS_DEBUG=true
GITNESS_DOCKER_API_VERSION=1.45
GITNESS_DOCKER_API_VERSION=1.41
GITNESS_SSH_ENABLE=true
GITNESS_SSH_HOST=localhost
GITNESS_SSH_PORT=2222
GITNESS_REGISTRY_STORAGE_TYPE=filesystem
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp
#GITNESS_DATABASE_DRIVER=postgres
#GITNESS_DATABASE_DATASOURCE=postgres://postgres:postgres@localhost:5432/gitness?sslmode=disable
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp

View File

@ -1,28 +0,0 @@
GET {{baseurl}}/repos/root/{{repo}}/+/diff/{{targetBranch}}...{{sourceBranch}}
Accept: text/plain
Authorization: {{token}}
### Get diff ignore white space
GET {{baseurl}}/repos/root/{{repo}}/+/diff/{{targetBranch}}...{{sourceBranch}}?ignore_whitespace=true
Accept: text/plain
Authorization: {{token}}
### Get diff with hidden white spaces
GET {{baseurl}}/repos/root/{{repo}}/+/commits/{{commit}}/diff
Accept: text/plain
Authorization: {{token}}
### Get commit diff ignore white space
GET {{baseurl}}/repos/root/{{repo}}/+/commits/{{commit}}/diff?ignore_whitespace=true
Accept: text/plain
Authorization: {{token}}
### Get diff stats
GET {{baseurl}}/repos/root/{{repo}}/+/diff-stats/{{targetBranch}}...{{sourceBranch}}
Accept: text/plain
Authorization: {{token}}
### Get diff stats ignore white space
GET {{baseurl}}/repos/root/{{repo}}/+/diff-stats/{{targetBranch}}...{{sourceBranch}}?ignore_whitespace=true
Accept: text/plain
Authorization: {{token}}

View File

@ -1,5 +0,0 @@
{
"dev": {
"baseurl": "http://localhost:3000/api/v1"
}
}

View File

@ -1,7 +0,0 @@
POST {{baseurl}}/login
Content-Type: application/json
{
"login_identifier": "{{login_identifier}}",
"password": "{{password}}"
}

View File

@ -1,4 +0,0 @@
### Get metric for space
GET {{baseurl}}/spaces/root/+/usage/metric
Authorization: {{token}}

View File

@ -18,7 +18,7 @@ RUN yarn && yarn build && yarn cache clean
# ---------------------------------------------------------#
# Build Harness image #
# ---------------------------------------------------------#
FROM --platform=$BUILDPLATFORM golang:1.24.9-alpine3.22 as builder
FROM --platform=$BUILDPLATFORM golang:1.22-alpine3.18 as builder
RUN apk update \
&& apk add --no-cache protoc build-base git
@ -69,7 +69,7 @@ RUN apk --update add ca-certificates
# ---------------------------------------------------------#
# Create final image #
# ---------------------------------------------------------#
FROM --platform=$TARGETPLATFORM alpine/git:2.49.1 as final
FROM --platform=$TARGETPLATFORM alpine/git:2.43.0 as final
# setup app dir and its content
WORKDIR /app
@ -77,7 +77,6 @@ VOLUME /data
ENV XDG_CACHE_HOME /data
ENV GITNESS_GIT_ROOT /data
ENV GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY /data/registry
ENV GITNESS_DATABASE_DRIVER sqlite3
ENV GITNESS_DATABASE_DATASOURCE /data/database.sqlite
ENV GITNESS_METRIC_ENABLED=true

View File

@ -1,86 +0,0 @@
# ---------------------------------------------------------#
# Pull UIv2 image #
# ---------------------------------------------------------#
# opensource-ui is only pushed as linux/arm64 - okay since we only copy files.
FROM --platform=linux/arm64 harness/opensource-ui:standalone.alpha.480 as uiv2
# ---------------------------------------------------------#
# Build Harness image #
# ---------------------------------------------------------#
FROM --platform=$BUILDPLATFORM golang:1.24.9-alpine3.22 as builder
RUN apk update \
&& apk add --no-cache protoc build-base git
# Setup workig dir
WORKDIR /app
RUN git config --global --add safe.directory '/app'
# Get dependencies - will also be cached if we won't change mod/sum
COPY go.mod .
COPY go.sum .
COPY Makefile .
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN make dep
RUN make tools
# COPY the source code as the last step
COPY . .
COPY --from=uiv2 /canary-dist /app/web/dist
# build
ARG GIT_COMMIT
ARG GITNESS_VERSION_MAJOR
ARG GITNESS_VERSION_MINOR
ARG GITNESS_VERSION_PATCH
ARG TARGETOS TARGETARCH
RUN if [ "$TARGETARCH" = "arm64" ]; then \
wget -P ~ https://musl.cc/aarch64-linux-musl-cross.tgz && \
tar -xvf ~/aarch64-linux-musl-cross.tgz -C ~ ; \
fi
# set required build flags
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
if [ "$TARGETARCH" = "arm64" ]; then CC=~/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc; fi && \
LDFLAGS="-X github.com/harness/gitness/version.GitCommit=${GIT_COMMIT} -X github.com/harness/gitness/version.major=${GITNESS_VERSION_MAJOR} -X github.com/harness/gitness/version.minor=${GITNESS_VERSION_MINOR} -X github.com/harness/gitness/version.patch=${GITNESS_VERSION_PATCH} -extldflags '-static'" && \
CGO_ENABLED=1 \
GOOS=$TARGETOS GOARCH=$TARGETARCH \
CC=$CC go build -ldflags="$LDFLAGS" -o ./gitness ./cmd/gitness
### Pull CA Certs
FROM --platform=$BUILDPLATFORM alpine:latest as cert-image
RUN apk --update add ca-certificates
# ---------------------------------------------------------#
# Create final image #
# ---------------------------------------------------------#
FROM --platform=$TARGETPLATFORM alpine/git:2.49.1 as final
# setup app dir and its content
WORKDIR /app
VOLUME /data
ENV XDG_CACHE_HOME /data
ENV GITNESS_GIT_ROOT /data
ENV GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY /data/registry
ENV GITNESS_DATABASE_DRIVER sqlite3
ENV GITNESS_DATABASE_DATASOURCE /data/database.sqlite
ENV GITNESS_METRIC_ENABLED=true
ENV GITNESS_METRIC_ENDPOINT=https://stats.drone.ci/api/v1/gitness
ENV GITNESS_TOKEN_COOKIE_NAME=token
ENV GITNESS_DOCKER_API_VERSION 1.41
ENV GITNESS_SSH_ENABLE=true
ENV GITNESS_GITSPACE_ENABLE=true
COPY --from=builder /app/gitness /app/gitness
COPY --from=cert-image /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 3000
EXPOSE 3022
ENTRYPOINT [ "/app/gitness", "server" ]

View File

@ -38,18 +38,14 @@ tools: $(tools) ## Install tools required for the build
#
###############################################################################
web-build: ## Build the web frontend
@echo "Building web frontend"
@cd web && yarn install && yarn build
build: generate ## Build the all-in-one Harness binary
@echo "Building Harness Server"
go build -o ./gitness ./cmd/gitness
test: generate ## Run the go tests
@echo "Running tests"
@go test -v -coverprofile=coverage.out `go list ./... | egrep -v "./registry/tests/(maven|cargo|gopkg|npm)"`
@go tool cover -html=coverage.out
go test -v -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
@ -62,35 +58,19 @@ test: generate ## Run the go tests
run: ar-clean build
./gitness server .local.env || true
# Main conformance test targets
ar-conformance-test: ar-clean build
./gitness server .local.env > logfile.log 2>&1 & echo $$! > server.PID
sleep 20
@sleep 10
./registry/tests/conformance_test.sh localhost:3000
@EXIT_CODE=$$?;
@kill `cat server.PID` 2>/dev/null || true
@rm -f server.PID
@rm -f logfile.log
@exit $$EXIT_CODE
EXIT_CODE=$$?;
kill `cat server.PID`
@rm server.PID
@rm logfile.log
exit $$EXIT_CODE
ar-hot-conformance-test:
@echo "Running OCI conformance tests..."
rm -rf distribution-spec || true
./registry/tests/conformance_test.sh localhost:3000 || true
@echo "Running Maven conformance tests..."
./registry/tests/maven/scripts/setup_test.sh localhost:3000
@chmod +x /tmp/maven_env.sh
source /tmp/maven_env.sh && go test -v ./registry/tests/maven/... -ginkgo.v || true
@echo "Running Cargo conformance tests..."
./registry/tests/cargo/scripts/setup_test.sh localhost:3000
@chmod +x /tmp/cargo_env.sh
source /tmp/cargo_env.sh && go test -v ./registry/tests/cargo/... -ginkgo.v || true
@chmod +x /tmp/go_env.sh
source /tmp/go_env.sh && go test -v ./registry/tests/gopkg/... -ginkgo.v || true
@echo "Running NPM conformance tests..."
./registry/tests/npm/scripts/setup_test.sh localhost:3000
@chmod +x /tmp/npm_env.sh
source /tmp/npm_env.sh && go test -v ./registry/tests/npm/... -ginkgo.v || true
ar-api-update:
@set -e; \
@ -113,31 +93,18 @@ ar-clean:
###############################################################################
format: tools # Format go code and error if any changes are made
@echo "Formatting ..."
@echo "Formating ..."
@goimports -w .
@gci write --skip-generated --custom-order -s standard -s "prefix(github.com/harness/gitness)" -s default -s blank -s dot .
@echo "Formatting complete"
modernize:
@echo "Modernizing ..."
@go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
sec:
@echo "Vulnerability detection $(1)"
@govulncheck ./...
lint: tools generate # lint the golang code - CI
lint: tools generate # lint the golang code
@echo "Linting $(1)"
@golangci-lint run --timeout=5m --verbose --new-from-rev=HEAD~ --whole-files
lint-full: tools generate # full linting the golang code
@echo "Linting $(1)"
@golangci-lint run --timeout=5m --verbose
lint-local: tools generate # lint the golang code - only untracked and staged changes
@echo "Linting $(1)"
@golangci-lint run --new-from-merge-base=main --new --timeout=5m --verbose --whole-files
@golangci-lint run --timeout=3m --verbose
###############################################################################
# Code Generation
@ -172,7 +139,7 @@ delete-tools: ## Delete the tools
# Install golangci-lint
$(GOBIN)/golangci-lint:
@echo "🔘 Installing golangci-lint... (`date '+%H:%M:%S'`)"
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v2.4.0
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.56.2
# Install goimports to format code
$(GOBIN)/goimports:
@ -188,7 +155,7 @@ $(GOBIN)/dbmate:
go install github.com/amacneil/dbmate@v1.15.0
$(GOBIN)/govulncheck:
go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
go install golang.org/x/vuln/cmd/govulncheck@v1.1.1
$(GOBIN)/protoc-gen-go:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
@ -197,7 +164,7 @@ $(GOBIN)/protoc-gen-go-grpc:
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
$(GOBIN)/gci:
go install github.com/daixiang0/gci@v0.13.7
go install github.com/daixiang0/gci@v0.13.1
help: ## show help message
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% 0-9a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

View File

@ -13,7 +13,7 @@ To install Harness yourself, simply run the command below. Once the container is
```bash
docker run -d \
-p 3000:3000 \
-p 3022:3022 \
-p 22:22 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/harness:/data \
--name harness \
@ -22,19 +22,19 @@ docker run -d \
```
> The Harness image uses a volume to store the database and repositories. It is highly recommended to use a bind mount or named volume as otherwise all data will be lost once the container is stopped.
See [developer.harness.io](https://developer.harness.io/docs/open-source) to learn how to get the most out of Harness.
See [developer.harness.com](https://developer.harness.io/docs/open-source) to learn how to get the most out of Harness.
## Where is Drone?
Harness Open Source represents a massive investment in the next generation of Drone. Where Drone focused solely on continuous integration, Harness adds source code hosting, developer environments (gitspaces), and artifact registries; providing teams with an end-to-end, open source DevOps platform.
The goal is for Harness to eventually be at full parity with Drone in terms of pipeline capabilities, allowing users to seamlessly migrate from Drone to Harness.
The goal is for Harness to eventually be at full parity with Drone in terms of pipeline capabilities, allowing users to seemlessly migrate from Drone to Harness.
But, we expect this to take some time, which is why we took a snapshot of Drone as a feature branch [drone](https://github.com/harness/harness/tree/drone) ([README](https://github.com/harness/harness/blob/drone/.github/readme.md)) so it can continue development.
As for Harness, the development is taking place on the [main](https://github.com/harness/harness/tree/main) branch.
For more information on Harness, please visit [developer.harness.io](https://developer.harness.io/).
For more information on Harness, please visit [developer.harness.com](https://developer.harness.com/).
For more information on Drone, please visit [drone.io](https://www.drone.io/).

View File

@ -30,19 +30,19 @@ import (
)
var (
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
ErrNotAuthorized = errors.New("not authorized")
ErrParentResourceTypeUnknown = errors.New("Unknown parent resource type")
ErrPrincipalTypeUnknown = errors.New("Unknown principal type")
)
// Check checks if a resource specific permission is granted for the current auth session in the scope.
// Returns nil if the permission is granted, otherwise returns an error.
// NotAuthenticated, NotAuthorized, or any underlying error.
func Check(
ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
scope *types.Scope, resource *types.Resource, permission enum.Permission,
) error {
authenticated, err := authorizer.Check(
authorized, err := authorizer.Check(
ctx,
session,
scope,
@ -53,16 +53,21 @@ func Check(
return err
}
return CheckSessionAuth(session, authenticated)
if !authorized {
return ErrNotAuthorized
}
return nil
}
// CheckAll checks if multiple resources specific permission is granted for the current auth session in the scope.
// Returns nil if the permission is granted, otherwise returns an error.
// NotAuthenticated, NotAuthorized, or any underlying error.
func CheckAll(
ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
permissionChecks ...types.PermissionCheck,
) error {
hasPermission, err := authorizer.CheckAll(
authorized, err := authorizer.CheckAll(
ctx,
session,
permissionChecks...,
@ -70,27 +75,14 @@ func CheckAll(
if err != nil {
return err
}
return CheckSessionAuth(session, hasPermission)
}
// CheckSessionAuth returns nil if the user is authenticated.
// Otherwise, ir returns err unauthorized on anonymous or err forbidden on non anonymous session.
func CheckSessionAuth(session *auth.Session, authenticated bool) error {
if !authenticated {
if auth.IsAnonymousSession(session) {
return ErrUnauthorized
}
return ErrForbidden
if !authorized {
return ErrNotAuthorized
}
return nil
}
// IsNoAccess returns true if the error is ErrUnauthorized or ErrForbidden.
func IsNoAccess(err error) bool {
return errors.Is(err, ErrForbidden) || errors.Is(err, ErrUnauthorized)
}
// CheckChild checks if a resource specific permission is granted for the current auth session
// in the scope of a parent.
// Returns nil if the permission is granted, otherwise returns an error.

View File

@ -17,14 +17,14 @@ package auth
import (
"context"
"fmt"
"slices"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/pkg/errors"
)
// CheckRepo checks if a repo specific permission is granted for the current auth session
@ -35,7 +35,7 @@ func CheckRepo(
ctx context.Context,
authorizer authz.Authorizer,
session *auth.Session,
repo *types.RepositoryCore,
repo *types.Repository,
permission enum.Permission,
) error {
parentSpace, name, err := paths.DisectLeaf(repo.Path)
@ -56,65 +56,13 @@ func IsRepoOwner(
ctx context.Context,
authorizer authz.Authorizer,
session *auth.Session,
repo *types.RepositoryCore,
repo *types.Repository,
) (bool, error) {
// for now we use repoedit as permission to verify if someone is a SpaceOwner and hence a RepoOwner.
err := CheckRepo(ctx, authorizer, session, repo, enum.PermissionRepoEdit)
if err != nil && !IsNoAccess(err) {
if err != nil && !errors.Is(err, ErrNotAuthorized) {
return false, fmt.Errorf("failed to check access user access: %w", err)
}
return err == nil, nil
}
// CheckRepoState checks if requested permission is allowed given the state of the repository.
func CheckRepoState(
_ context.Context,
_ *auth.Session,
repo *types.RepositoryCore,
reqPermission enum.Permission,
additionalAllowedRepoStates ...enum.RepoState,
) error {
permissionsAllowedPerRepoState := map[enum.RepoState][]enum.Permission{
enum.RepoStateActive: {
enum.PermissionRepoView,
enum.PermissionRepoCreate,
enum.PermissionRepoEdit,
enum.PermissionRepoPush,
enum.PermissionRepoReview,
enum.PermissionRepoDelete,
enum.PermissionRepoReportCommitCheck,
enum.PermissionPipelineView,
enum.PermissionPipelineExecute,
enum.PermissionPipelineEdit,
enum.PermissionPipelineDelete,
enum.PermissionServiceAccountView,
},
enum.RepoStateArchived: {
enum.PermissionRepoView,
enum.PermissionPipelineView,
enum.PermissionServiceAccountView,
},
// allowed permissions for repos on transition states during import/migration are handled by their controller.
enum.RepoStateGitImport: {},
enum.RepoStateMigrateDataImport: {},
enum.RepoStateMigrateGitPush: {},
}
if len(additionalAllowedRepoStates) > 0 && slices.Contains(additionalAllowedRepoStates, repo.State) {
return nil
}
defaultAllowedPermissions := permissionsAllowedPerRepoState[repo.State]
if !slices.Contains(defaultAllowedPermissions, reqPermission) {
return errors.PreconditionFailedf("Operation is not allowed for repository in state %s", repo.State)
}
return nil
}

View File

@ -32,7 +32,7 @@ func CheckSpace(
ctx context.Context,
authorizer authz.Authorizer,
session *auth.Session,
space *types.SpaceCore,
space *types.Space,
permission enum.Permission,
) error {
parentSpace, name, err := paths.DisectLeaf(space.Path)
@ -56,7 +56,7 @@ func CheckSpaceScope(
ctx context.Context,
authorizer authz.Authorizer,
session *auth.Session,
space *types.SpaceCore,
space *types.Space,
resourceType enum.ResourceType,
permission enum.Permission,
) error {

View File

@ -0,0 +1,137 @@
// 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"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/controller"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/bootstrap"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/sha"
"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)
}
// ToDo: put actual values
payload := &CommitPayload{}
branch := ""
_, err = c.commit(ctx, session, repo, payload)
if err != nil {
return &types.AnalyseExecutionOutput{}, err
}
return &types.AnalyseExecutionOutput{Branch: branch, Summary: ""}, nil
}
type CommitPayload struct {
Title string
Message string
Branch string
NewBranch string
Files []*Files
}
type Files struct {
action git.FileAction
path string
content string
SHA sha.SHA
}
func (c *Controller) commit(ctx context.Context,
session *auth.Session,
repo *types.Repository,
payload *CommitPayload) (types.CommitFilesResponse, error) {
files := payload.Files
actions := make([]git.CommitFileAction, len(files))
for i, file := range files {
rawPayload := []byte(file.content)
actions[i] = git.CommitFileAction{
Action: file.action,
Path: file.path,
Payload: rawPayload,
SHA: file.SHA,
}
}
writeParams, err := controller.CreateRPCInternalWriteParams(ctx, c.urlProvider, session, repo)
if err != nil {
return types.CommitFilesResponse{}, fmt.Errorf("failed to create RPC write params: %w", err)
}
now := time.Now()
commit, err := c.git.CommitFiles(ctx, &git.CommitFilesParams{
WriteParams: writeParams,
Message: git.CommitMessage(payload.Title, payload.Message),
Branch: payload.Branch,
NewBranch: payload.NewBranch,
Actions: actions,
Committer: identityFromPrincipal(bootstrap.NewSystemServiceSession().Principal),
CommitterDate: &now,
Author: identityFromPrincipal(session.Principal),
AuthorDate: &now,
})
if err != nil {
return types.CommitFilesResponse{}, err
}
return types.CommitFilesResponse{
CommitID: commit.CommitID.String(),
}, nil
}
func identityFromPrincipal(p types.Principal) *git.Identity {
return &git.Identity{
Name: p.DisplayName,
Email: p.Email,
}
}

View File

@ -0,0 +1,57 @@
// 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/services/messaging"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/git"
)
type Controller struct {
authorizer authz.Authorizer
intelligenceService *aiagent.HarnessIntelligence
repoStore store.RepoStore
pipelineStore store.PipelineStore
executionStore store.ExecutionStore
git git.Interface
urlProvider url.Provider
slackbot *messaging.Slack
}
func NewController(
authorizer authz.Authorizer,
pipeline *aiagent.HarnessIntelligence,
repoStore store.RepoStore,
pipelineStore store.PipelineStore,
executionStore store.ExecutionStore,
git git.Interface,
urlProvider url.Provider,
slackbot *messaging.Slack,
) *Controller {
return &Controller{
authorizer: authorizer,
intelligenceService: pipeline,
repoStore: repoStore,
pipelineStore: pipelineStore,
executionStore: executionStore,
git: git,
urlProvider: urlProvider,
slackbot: slackbot,
}
}

View File

@ -0,0 +1,63 @@
// 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 PipelineData struct {
YamlPipeline string `json:"yaml_pipeline"`
}
type GeneratePipelineOutput struct {
Status string `json:"status"`
Data PipelineData `json:"data"`
}
func (c *Controller) GeneratePipeline(
ctx context.Context,
in *GeneratePipelineInput,
) (*GeneratePipelineOutput, error) {
generateRequest := &types.PipelineGenerateRequest{
Prompt: in.Prompt,
RepoRef: in.RepoRef,
}
// do permission check on repo here?
repo, err := c.repoStore.FindByRef(ctx, in.RepoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
output, err := c.intelligenceService.Generate(ctx, generateRequest, repo)
if err != nil {
return nil, fmt.Errorf("generate pipeline: %w", err)
}
return &GeneratePipelineOutput{
Status: "SUCCESS",
Data: PipelineData{
YamlPipeline: output.YAML,
},
}, nil
}

View File

@ -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/slack-go/slack/slackevents"
)
type SlackbotOutput struct {
Success bool
}
func (c *Controller) HandleEvent(
_ context.Context,
eventsAPIEvent slackevents.EventsAPIEvent) (*SlackbotOutput, error) {
if eventsAPIEvent.Type == slackevents.CallbackEvent {
success, err := c.HandleCallbackEvent(eventsAPIEvent.InnerEvent)
if err != nil {
return nil, err
}
return &SlackbotOutput{Success: success}, nil
}
return nil, fmt.Errorf("unknown event type: %s", eventsAPIEvent.Type)
}
func (c *Controller) HandleCallbackEvent(innerEvent slackevents.EventsAPIInnerEvent) (bool, error) {
switch innerEvent.Data.(type) {
case *slackevents.AppMentionEvent:
default:
// no action needed for unhandled event types
}
return true, nil
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package infraprovider
package aiagent
import (
"context"
@ -21,19 +21,23 @@ import (
"github.com/harness/gitness/types"
)
func (c *Service) List(
ctx context.Context,
filter *types.InfraProviderConfigFilter,
) ([]*types.InfraProviderConfig, error) {
infraProviderConfigs, err := c.infraProviderConfigStore.List(ctx, filter)
if err != nil {
return nil, fmt.Errorf("failed to list infraprovider configs: %w", err)
}
for _, infraProviderConfig := range infraProviderConfigs {
err = c.populateDetails(ctx, infraProviderConfig)
if err != nil {
return nil, err
}
}
return infraProviderConfigs, nil
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.intelligenceService.Suggest(ctx, suggestionRequest)
if err != nil {
return nil, fmt.Errorf("suggest pipeline: %w", err)
}
return output, nil
}

View File

@ -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 aiagent
import (
"context"
"fmt"
"github.com/harness/gitness/types"
)
type UpdatePipelineOutput struct {
Status string `json:"status"`
Data PipelineData `json:"data"`
}
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,
) (*UpdatePipelineOutput, error) {
generateRequest := &types.PipelineUpdateRequest{
Prompt: in.Prompt,
RepoRef: in.RepoRef,
Pipeline: in.Pipeline,
}
// do permission check on repo here?
repo, err := c.repoStore.FindByRef(ctx, in.RepoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
output, err := c.intelligenceService.Update(ctx, generateRequest, repo)
if err != nil {
return nil, fmt.Errorf("update pipeline: %w", err)
}
return &UpdatePipelineOutput{
Status: "SUCCESS",
Data: PipelineData{
YamlPipeline: output.YAML,
},
}, nil
}

View File

@ -12,44 +12,42 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package lfs
package aiagent
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/services/remoteauth"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/services/aiagent"
"github.com/harness/gitness/app/services/messaging"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/blob"
"github.com/harness/gitness/git"
"github.com/google/wire"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
ProvideController,
)
func ProvideController(
authorizer authz.Authorizer,
repoFinder refcache.RepoFinder,
aiagentPipeline *aiagent.HarnessIntelligence,
repoStore store.RepoStore,
principalStore store.PrincipalStore,
lfsStore store.LFSObjectStore,
blobStore blob.Store,
remoteAuth remoteauth.Service,
pipelineStore store.PipelineStore,
executionStore store.ExecutionStore,
git git.Interface,
urlProvider url.Provider,
settings *settings.Service,
slackbot *messaging.Slack,
) *Controller {
return NewController(
authorizer,
repoFinder,
aiagentPipeline,
repoStore,
principalStore,
lfsStore,
blobStore,
remoteAuth,
pipelineStore,
executionStore,
git,
urlProvider,
settings,
slackbot,
)
}

View File

@ -12,24 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package usage
package capabilities
import (
"github.com/harness/gitness/types"
"github.com/harness/gitness/app/services/capabilities"
)
type Config struct {
MaxWorkers int
type Controller struct {
Capabilities *capabilities.Registry
}
func NewConfig(global *types.Config) Config {
cfg := Config{
MaxWorkers: global.UsageMetrics.MaxWorkers,
func NewController(
capabilities *capabilities.Registry,
) *Controller {
return &Controller{
Capabilities: capabilities,
}
if cfg.MaxWorkers == 0 {
cfg.MaxWorkers = 5
}
return cfg
}

View File

@ -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
}

View File

@ -12,17 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package platformconnector
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(
ProvideGitnessPlatformConnector,
ProvideController,
)
func ProvideGitnessPlatformConnector() PlatformConnector {
return NewGitnessPlatformConnector()
func ProvideController(
capabilities *capabilities.Registry,
) *Controller {
return NewController(
capabilities,
)
}

View File

@ -1,63 +0,0 @@
// 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 check
import (
"context"
"fmt"
"time"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListRecentChecksSpace return an array of status check UIDs that have been run recently.
func (c *Controller) ListRecentChecksSpace(
ctx context.Context,
session *auth.Session,
spaceRef string,
recursive bool,
opts types.CheckRecentOptions,
) ([]string, error) {
space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to space: %w", err)
}
if opts.Since == 0 {
opts.Since = time.Now().Add(-30 * 24 * time.Hour).UnixMilli()
}
var spaceIDs []int64
if recursive {
spaceIDs, err = c.spaceStore.GetDescendantsIDs(ctx, space.ID)
if err != nil {
return nil, fmt.Errorf("failed to get space descendants ids: %w", err)
}
} else {
spaceIDs = append(spaceIDs, space.ID)
}
checkIdentifiers, err := c.checkStore.ListRecentSpace(ctx, spaceIDs, opts)
if err != nil {
return nil, fmt.Errorf(
"failed to list status check results for space=%s: %w",
space.Identifier, err,
)
}
return checkIdentifiers, nil
}

View File

@ -25,7 +25,6 @@ import (
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
events "github.com/harness/gitness/app/events/check"
"github.com/harness/gitness/git"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
@ -82,7 +81,7 @@ func (in *ReportInput) Sanitize(
}
if in.Ended != 0 && in.Ended < in.Started {
return usererror.BadRequest("Started time reported after ended time")
return usererror.BadRequest("started time reported after ended time")
}
return nil
@ -137,7 +136,7 @@ func (c *Controller) Report(
}
if !git.ValidateCommitSHA(commitSHA) {
return nil, usererror.BadRequest("Invalid commit SHA provided")
return nil, usererror.BadRequest("invalid commit SHA provided")
}
_, err = c.git.GetCommit(ctx, &git.GetCommitParams{
@ -183,17 +182,6 @@ func (c *Controller) Report(
return nil, fmt.Errorf("failed to upsert status check result for repo=%s: %w", repo.Identifier, err)
}
c.eventReporter.Reported(ctx, &events.ReportedPayload{
Base: events.Base{
RepoID: repo.ID,
SHA: commitSHA,
},
Identifier: in.Identifier,
Status: in.Status,
})
c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeStatusCheckReportUpdated, statusCheckReport)
return statusCheckReport, nil
}

View File

@ -19,13 +19,9 @@ import (
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
checkevents "github.com/harness/gitness/app/events/check"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/git"
"github.com/harness/gitness/store/database/dbtx"
@ -34,77 +30,47 @@ import (
)
type Controller struct {
tx dbtx.Transactor
authorizer authz.Authorizer
spaceStore store.SpaceStore
checkStore store.CheckStore
spaceFinder refcache.SpaceFinder
repoFinder refcache.RepoFinder
git git.Interface
sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error
sseStreamer sse.Streamer
eventReporter *checkevents.Reporter
tx dbtx.Transactor
authorizer authz.Authorizer
repoStore store.RepoStore
checkStore store.CheckStore
git git.Interface
sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error
}
func NewController(
tx dbtx.Transactor,
authorizer authz.Authorizer,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
checkStore store.CheckStore,
spaceFinder refcache.SpaceFinder,
repoFinder refcache.RepoFinder,
git git.Interface,
sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error,
sseStreamer sse.Streamer,
eventReporter *checkevents.Reporter,
) *Controller {
return &Controller{
tx: tx,
authorizer: authorizer,
spaceStore: spaceStore,
checkStore: checkStore,
spaceFinder: spaceFinder,
repoFinder: repoFinder,
git: git,
sanitizers: sanitizers,
sseStreamer: sseStreamer,
eventReporter: eventReporter,
tx: tx,
authorizer: authorizer,
repoStore: repoStore,
checkStore: checkStore,
git: git,
sanitizers: sanitizers,
}
}
//nolint:unparam
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.RepositoryCore, error) {
func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session, repoRef string, reqPermission enum.Permission,
) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
}
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
return repo, nil
}
func (c *Controller) getSpaceCheckAccess(
ctx context.Context,
session *auth.Session,
spaceRef string,
permission enum.Permission,
) (*types.SpaceCore, error) {
return space.GetSpaceCheckAuth(ctx, c.spaceFinder, c.authorizer, session, spaceRef, permission)
}

View File

@ -17,9 +17,6 @@ package check
import (
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
checkevents "github.com/harness/gitness/app/events/check"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/git"
"github.com/harness/gitness/store/database/dbtx"
@ -37,25 +34,17 @@ var WireSet = wire.NewSet(
func ProvideController(
tx dbtx.Transactor,
authorizer authz.Authorizer,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
checkStore store.CheckStore,
spaceFinder refcache.SpaceFinder,
repoFinder refcache.RepoFinder,
git git.Interface,
rpcClient git.Interface,
sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error,
sseStreamer sse.Streamer,
eventReporter *checkevents.Reporter,
) *Controller {
return NewController(
tx,
authorizer,
spaceStore,
repoStore,
checkStore,
spaceFinder,
repoFinder,
git,
rpcClient,
sanitizers,
sseStreamer,
eventReporter,
)
}

View File

@ -17,27 +17,27 @@ package connector
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/connector"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
)
type Controller struct {
connectorStore store.ConnectorStore
connectorService *connector.Service
spaceFinder refcache.SpaceFinder
authorizer authz.Authorizer
authorizer authz.Authorizer
spaceStore store.SpaceStore
}
func NewController(
authorizer authz.Authorizer,
connectorStore store.ConnectorStore,
connectorService *connector.Service,
spaceFinder refcache.SpaceFinder,
spaceStore store.SpaceStore,
) *Controller {
return &Controller{
connectorStore: connectorStore,
connectorService: connectorService,
spaceFinder: spaceFinder,
authorizer: authorizer,
spaceStore: spaceStore,
}
}

View File

@ -52,7 +52,7 @@ func (c *Controller) Create(
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
parentSpace, err := c.spaceFinder.FindByRef(ctx, in.SpaceRef)
parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
@ -99,7 +99,7 @@ func (in *CreateInput) validate() error {
// check that the connector type is valid
if _, ok := in.Type.Sanitize(); !ok {
return usererror.BadRequest("Invalid connector type")
return usererror.BadRequest("invalid connector type")
}
// if the connector type is valid, validate the connector config

View File

@ -29,7 +29,7 @@ func (c *Controller) Delete(
spaceRef string,
identifier string,
) error {
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return fmt.Errorf("failed to find space: %w", err)
}

View File

@ -30,20 +30,17 @@ func (c *Controller) Find(
spaceRef string,
identifier string,
) (*types.Connector, error) {
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckConnector(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionConnectorView)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
connector, err := c.connectorStore.FindByIdentifier(ctx, space.ID, identifier)
if err != nil {
return nil, fmt.Errorf("failed to find connector: %w", err)
}
return connector, nil
}

View File

@ -33,16 +33,14 @@ func (c *Controller) Test(
spaceRef string,
identifier string,
) (types.ConnectorTestResponse, error) {
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return types.ConnectorTestResponse{}, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckConnector(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionConnectorAccess)
if err != nil {
return types.ConnectorTestResponse{}, fmt.Errorf("failed to authorize: %w", err)
}
connector, err := c.connectorStore.FindByIdentifier(ctx, space.ID, identifier)
if err != nil {
return types.ConnectorTestResponse{}, fmt.Errorf("failed to find connector: %w", err)

View File

@ -45,7 +45,7 @@ func (c *Controller) Update(
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
@ -74,7 +74,7 @@ func (c *Controller) Update(
// We can revisit this once we start adding more connectors.
if in.ConnectorConfig != nil {
if err := in.ConnectorConfig.Validate(connector.Type); err != nil {
return usererror.BadRequestf("Failed to validate connector config: %s", err.Error())
return usererror.BadRequestf("failed to validate connector config: %s", err.Error())
}
original.ConnectorConfig = *in.ConnectorConfig
}

View File

@ -17,7 +17,6 @@ package connector
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/connector"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/google/wire"
@ -32,7 +31,7 @@ func ProvideController(
connectorStore store.ConnectorStore,
connectorService *connector.Service,
authorizer authz.Authorizer,
spaceFinder refcache.SpaceFinder,
spaceStore store.SpaceStore,
) *Controller {
return NewController(authorizer, connectorStore, connectorService, spaceFinder)
return NewController(authorizer, connectorStore, connectorService, spaceStore)
}

View File

@ -18,6 +18,7 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/pipeline/checks"
"github.com/harness/gitness/types"
@ -33,15 +34,13 @@ func (c *Controller) Cancel(
pipelineIdentifier string,
executionNum int64,
) (*types.Execution, error) {
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineExecute,
)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineExecute)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -15,20 +15,12 @@
package execution
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/pipeline/canceler"
"github.com/harness/gitness/app/pipeline/commit"
"github.com/harness/gitness/app/pipeline/triggerer"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type Controller struct {
@ -39,9 +31,9 @@ type Controller struct {
canceler canceler.Canceler
commitService commit.Service
triggerer triggerer.Triggerer
repoStore store.RepoStore
stageStore store.StageStore
pipelineStore store.PipelineStore
repoFinder refcache.RepoFinder
}
func NewController(
@ -52,9 +44,9 @@ func NewController(
canceler canceler.Canceler,
commitService commit.Service,
triggerer triggerer.Triggerer,
repoStore store.RepoStore,
stageStore store.StageStore,
pipelineStore store.PipelineStore,
repoFinder refcache.RepoFinder,
) *Controller {
return &Controller{
tx: tx,
@ -64,43 +56,8 @@ func NewController(
canceler: canceler,
commitService: commitService,
triggerer: triggerer,
repoStore: repoStore,
stageStore: stageStore,
pipelineStore: pipelineStore,
repoFinder: repoFinder,
}
}
// getRepoCheckPipelineAccess fetches a repo, checks if the permission is allowed based on the repo state,
// and checks if the current user has permission to access pipelines belong to it.
//
//nolint:unparam
func (c *Controller) getRepoCheckPipelineAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
pipelineIdentifier string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.RepositoryCore, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
err = apiauth.CheckPipeline(
ctx,
c.authorizer,
session,
repo.Path,
pipelineIdentifier,
reqPermission)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
return repo, nil
}

View File

@ -18,6 +18,7 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/pipeline/triggerer"
"github.com/harness/gitness/types"
@ -33,9 +34,14 @@ func (c *Controller) Create(
pipelineIdentifier string,
branch string,
) (*types.Execution, error) {
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, pipelineIdentifier, enum.PermissionPipelineExecute)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path,
pipelineIdentifier, enum.PermissionPipelineExecute)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)
@ -70,8 +76,8 @@ func (c *Controller) Create(
Ref: ref,
Message: commit.Message,
Title: commit.Title,
Before: commit.SHA.String(),
After: commit.SHA.String(),
Before: commit.SHA,
After: commit.SHA,
Sender: session.Principal.UID,
Source: branch,
Target: branch,

View File

@ -18,6 +18,7 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
@ -29,26 +30,22 @@ func (c *Controller) Delete(
pipelineIdentifier string,
executionNum int64,
) error {
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineDelete,
)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return err
return fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineDelete)
if err != nil {
return fmt.Errorf("failed to authorize: %w", err)
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)
if err != nil {
return fmt.Errorf("failed to find pipeline: %w", err)
}
err = c.executionStore.Delete(ctx, pipeline.ID, executionNum)
if err != nil {
return fmt.Errorf("could not delete execution: %w", err)
}
return nil
}

View File

@ -18,6 +18,7 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -30,15 +31,13 @@ func (c *Controller) Find(
pipelineIdentifier string,
executionNum int64,
) (*types.Execution, error) {
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineView,
)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -18,6 +18,7 @@ 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"
@ -31,15 +32,14 @@ func (c *Controller) List(
pipelineIdentifier string,
pagination types.Pagination,
) ([]*types.Execution, int64, error) {
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineView,
)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, 0, err
return nil, 0, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, 0, fmt.Errorf("failed to authorize: %w", err)
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -19,7 +19,6 @@ import (
"github.com/harness/gitness/app/pipeline/canceler"
"github.com/harness/gitness/app/pipeline/commit"
"github.com/harness/gitness/app/pipeline/triggerer"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
@ -39,10 +38,10 @@ func ProvideController(
canceler canceler.Canceler,
commitService commit.Service,
triggerer triggerer.Triggerer,
repoStore store.RepoStore,
stageStore store.StageStore,
pipelineStore store.PipelineStore,
repoFinder refcache.RepoFinder,
) *Controller {
return NewController(tx, authorizer, executionStore, checkStore,
canceler, commitService, triggerer, stageStore, pipelineStore, repoFinder)
canceler, commitService, triggerer, repoStore, stageStore, pipelineStore)
}

View File

@ -22,16 +22,12 @@ import (
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
gitevents "github.com/harness/gitness/app/events/git"
repoevents "github.com/harness/gitness/app/events/repo"
eventsgit "github.com/harness/gitness/app/events/git"
eventsrepo "github.com/harness/gitness/app/events/repo"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/services/usergroup"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/api"
@ -39,17 +35,15 @@ import (
"github.com/harness/gitness/git/sha"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type Controller struct {
authorizer authz.Authorizer
principalStore store.PrincipalStore
repoStore store.RepoStore
repoFinder refcache.RepoFinder
gitReporter *gitevents.Reporter
repoReporter *repoevents.Reporter
gitReporter *eventsgit.Reporter
repoReporter *eventsrepo.Reporter
git git.Interface
pullreqStore store.PullReqStore
urlProvider url.Provider
protectionManager *protection.Manager
@ -58,19 +52,15 @@ type Controller struct {
preReceiveExtender PreReceiveExtender
updateExtender UpdateExtender
postReceiveExtender PostReceiveExtender
sseStreamer sse.Streamer
lfsStore store.LFSObjectStore
auditService audit.Service
userGroupService usergroup.Service
}
func NewController(
authorizer authz.Authorizer,
principalStore store.PrincipalStore,
repoStore store.RepoStore,
repoFinder refcache.RepoFinder,
gitReporter *gitevents.Reporter,
repoReporter *repoevents.Reporter,
gitReporter *eventsgit.Reporter,
repoReporter *eventsrepo.Reporter,
git git.Interface,
pullreqStore store.PullReqStore,
urlProvider url.Provider,
protectionManager *protection.Manager,
@ -79,18 +69,14 @@ func NewController(
preReceiveExtender PreReceiveExtender,
updateExtender UpdateExtender,
postReceiveExtender PostReceiveExtender,
sseStreamer sse.Streamer,
lfsStore store.LFSObjectStore,
auditService audit.Service,
userGroupService usergroup.Service,
) *Controller {
return &Controller{
authorizer: authorizer,
principalStore: principalStore,
repoStore: repoStore,
repoFinder: repoFinder,
gitReporter: gitReporter,
repoReporter: repoReporter,
git: git,
pullreqStore: pullreqStore,
urlProvider: urlProvider,
protectionManager: protectionManager,
@ -99,28 +85,19 @@ func NewController(
preReceiveExtender: preReceiveExtender,
updateExtender: updateExtender,
postReceiveExtender: postReceiveExtender,
sseStreamer: sseStreamer,
lfsStore: lfsStore,
auditService: auditService,
userGroupService: userGroupService,
}
}
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
_ *auth.Session,
repoID int64,
_ enum.Permission,
) (*types.RepositoryCore, error) {
func (c *Controller) getRepoCheckAccess(ctx context.Context,
_ *auth.Session, repoID int64, _ enum.Permission) (*types.Repository, error) {
if repoID < 1 {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
}
repo, err := c.repoFinder.FindByID(ctx, repoID)
repo, err := c.repoStore.Find(ctx, repoID)
if err != nil {
return nil, fmt.Errorf("failed to find repo with id %d: %w", repoID, err)
}
// repo state check is done in pre-receive.
// TODO: execute permission check. block anything but Harness service?
@ -134,7 +111,7 @@ func (c *Controller) getRepoCheckAccess(
func GetBaseSHAForScanningChanges(
ctx context.Context,
rgit RestrictedGIT,
repo *types.RepositoryCore,
repo *types.Repository,
env hook.Environment,
refUpdates []hook.ReferenceUpdate,
findBaseFor hook.ReferenceUpdate,
@ -185,23 +162,19 @@ func isForcePush(
rgit RestrictedGIT,
gitUID string,
alternateObjectDirs []string,
refUpdate hook.ReferenceUpdate,
branchUpdate hook.ReferenceUpdate,
) (bool, error) {
if refUpdate.Old.IsNil() || refUpdate.New.IsNil() {
if branchUpdate.Old.IsNil() || branchUpdate.New.IsNil() {
return false, nil
}
if isTag(refUpdate.Ref) {
return true, nil
}
result, err := rgit.IsAncestor(ctx, git.IsAncestorParams{
ReadParams: git.ReadParams{
RepoUID: gitUID,
AlternateObjectDirs: alternateObjectDirs,
},
AncestorCommitSHA: refUpdate.Old,
DescendantCommitSHA: refUpdate.New,
AncestorCommitSHA: branchUpdate.Old,
DescendantCommitSHA: branchUpdate.New,
})
if err != nil {
return false, err
@ -209,29 +182,3 @@ func isForcePush(
return !result.Ancestor, nil
}
func logOutputFor(ctx context.Context, hookName string, output hook.Output) {
event := log.Ctx(ctx).Info()
if output.Error != nil {
event = event.Str("output.error", *output.Error)
}
if len(output.Messages) > 0 {
filteredMsgs := make([]string, 0, len(output.Messages)/2+1)
for _, msg := range output.Messages {
if msg == "" {
continue
}
filteredMsgs = append(filteredMsgs, msg)
}
const maxMessageLines = 16
if len(filteredMsgs) > maxMessageLines {
filteredMsgs = append(filteredMsgs[:maxMessageLines], fmt.Sprintf("... %d more", len(filteredMsgs)-maxMessageLines))
}
event = event.Strs("output.messages", filteredMsgs)
}
event.Msgf("%s hook output", hookName)
}

View File

@ -27,7 +27,7 @@ type PreReceiveExtender interface {
context.Context,
RestrictedGIT,
*auth.Session,
*types.RepositoryCore,
*types.Repository,
types.GithookPreReceiveInput,
*hook.Output,
) error
@ -38,7 +38,7 @@ type UpdateExtender interface {
context.Context,
RestrictedGIT,
*auth.Session,
*types.RepositoryCore,
*types.Repository,
types.GithookUpdateInput,
*hook.Output,
) error
@ -49,7 +49,7 @@ type PostReceiveExtender interface {
context.Context,
RestrictedGIT,
*auth.Session,
*types.RepositoryCore,
*types.Repository,
types.GithookPostReceiveInput,
*hook.Output,
) error
@ -66,7 +66,7 @@ func (NoOpPreReceiveExtender) Extend(
context.Context,
RestrictedGIT,
*auth.Session,
*types.RepositoryCore,
*types.Repository,
types.GithookPreReceiveInput,
*hook.Output,
) error {
@ -84,7 +84,7 @@ func (NoOpUpdateExtender) Extend(
context.Context,
RestrictedGIT,
*auth.Session,
*types.RepositoryCore,
*types.Repository,
types.GithookUpdateInput,
*hook.Output,
) error {
@ -102,7 +102,7 @@ func (NoOpPostReceiveExtender) Extend(
context.Context,
RestrictedGIT,
*auth.Session,
*types.RepositoryCore,
*types.Repository,
types.GithookPostReceiveInput,
*hook.Output,
) error {

View File

@ -32,9 +32,8 @@ type RestrictedGIT interface {
GetBranch(ctx context.Context, params *git.GetBranchParams) (*git.GetBranchOutput, error)
Diff(ctx context.Context, in *git.DiffParams, files ...api.FileDiffRequest) (<-chan *git.FileDiff, <-chan error)
GetBlob(ctx context.Context, params *git.GetBlobParams) (*git.GetBlobOutput, error)
ProcessPreReceiveObjects(
FindOversizeFiles(
ctx context.Context,
params git.ProcessPreReceiveObjectsParams,
) (git.ProcessPreReceiveObjectsOutput, error)
MergeBase(ctx context.Context, params git.MergeBaseParams) (git.MergeBaseOutput, error)
params *git.FindOversizeFilesParams,
) (*git.FindOversizeFilesOutput, error)
}

View File

@ -17,23 +17,14 @@ package githook
import (
"context"
"fmt"
"slices"
"strings"
"time"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/bootstrap"
gitevents "github.com/harness/gitness/app/events/git"
events "github.com/harness/gitness/app/events/git"
repoevents "github.com/harness/gitness/app/events/repo"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/git"
gitapi "github.com/harness/gitness/git/api"
gitenum "github.com/harness/gitness/git/enum"
"github.com/harness/gitness/git/hook"
"github.com/harness/gitness/git/sha"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -52,9 +43,6 @@ const (
gitReferenceNamePullReq = "refs/pullreq/"
)
// refForcePushMap stores branch refs that were force pushed.
type refForcePushMap map[string]struct{}
// PostReceive executes the post-receive hook for a git repository.
func (c *Controller) PostReceive(
ctx context.Context,
@ -62,52 +50,30 @@ func (c *Controller) PostReceive(
session *auth.Session,
in types.GithookPostReceiveInput,
) (hook.Output, error) {
repoCore, err := c.getRepoCheckAccess(ctx, session, in.RepoID, enum.PermissionRepoPush)
repo, err := c.getRepoCheckAccess(ctx, session, in.RepoID, enum.PermissionRepoPush)
if err != nil {
return hook.Output{}, err
}
repo, err := c.repoStore.Find(ctx, repoCore.ID)
if err != nil {
return hook.Output{}, err
}
// create output object and have following messages fill its messages
out := hook.Output{}
defer func() {
logOutputFor(ctx, "post-receive", out)
}()
// update default branch based on ref update info on empty repos.
// as the branch could be different than the configured default value.
c.handleEmptyRepoPush(ctx, repo, in.PostReceiveInput, &out)
// always update last git push time - best effort
c.updateLastGITPushTime(ctx, repo)
// report ref events if repo is in an active state - best effort
forcePushStatus := make(refForcePushMap)
// report ref events if repo is in an active state (best effort)
if repo.State == enum.RepoStateActive {
forcePushStatus = c.reportReferenceEvents(ctx, rgit, repo, in.PrincipalID, in.PostReceiveInput)
c.reportReferenceEvents(ctx, rgit, repo, in.PrincipalID, in.PostReceiveInput)
}
// handle branch updates related to PRs - best effort
c.handlePRMessaging(ctx, rgit, repo, in.PostReceiveInput, &out)
c.handlePRMessaging(ctx, repo, in.PostReceiveInput, &out)
err = c.postReceiveExtender.Extend(ctx, rgit, session, repo.Core(), in, &out)
err = c.postReceiveExtender.Extend(ctx, rgit, session, repo, in, &out)
if err != nil {
return hook.Output{}, fmt.Errorf("failed to extend post-receive hook: %w", err)
}
c.logForcePush(ctx, repo, in.PrincipalID, in.RefUpdates, forcePushStatus)
c.repoReporter.Pushed(ctx, &repoevents.PushedPayload{
Base: repoevents.Base{
RepoID: in.RepoID,
PrincipalID: in.PrincipalID,
},
})
return out, nil
}
@ -120,23 +86,17 @@ func (c *Controller) reportReferenceEvents(
repo *types.Repository,
principalID int64,
in hook.PostReceiveInput,
) refForcePushMap {
forcePushStatus := make(refForcePushMap)
) {
for _, refUpdate := range in.RefUpdates {
switch {
case strings.HasPrefix(refUpdate.Ref, gitReferenceNamePrefixBranch):
if forced := c.reportBranchEvent(ctx, rgit, repo, principalID, in.Environment, refUpdate); forced {
forcePushStatus[refUpdate.Ref] = struct{}{}
}
c.reportBranchEvent(ctx, rgit, repo, principalID, in.Environment, refUpdate)
case strings.HasPrefix(refUpdate.Ref, gitReferenceNamePrefixTag):
c.reportTagEvent(ctx, repo, principalID, refUpdate)
default:
// Ignore any other references in post-receive
}
}
return forcePushStatus
}
func (c *Controller) reportBranchEvent(
@ -146,39 +106,26 @@ func (c *Controller) reportBranchEvent(
principalID int64,
env hook.Environment,
branchUpdate hook.ReferenceUpdate,
) bool {
var forced bool
) {
switch {
case branchUpdate.Old.IsNil():
payload := &gitevents.BranchCreatedPayload{
c.gitReporter.BranchCreated(ctx, &events.BranchCreatedPayload{
RepoID: repo.ID,
PrincipalID: principalID,
Ref: branchUpdate.Ref,
SHA: branchUpdate.New.String(),
}
c.gitReporter.BranchCreated(ctx, payload)
c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeBranchCreated, payload)
})
case branchUpdate.New.IsNil():
payload := &gitevents.BranchDeletedPayload{
c.gitReporter.BranchDeleted(ctx, &events.BranchDeletedPayload{
RepoID: repo.ID,
PrincipalID: principalID,
Ref: branchUpdate.Ref,
SHA: branchUpdate.Old.String(),
}
c.gitReporter.BranchDeleted(ctx, payload)
c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeBranchDeleted, payload)
})
default:
// A force update event might trigger some additional operations that aren't required
// for ordinary updates (force pushes alter the commit history of a branch).
var err error
forced, err = isForcePush(ctx, rgit, repo.GitUID, env.AlternateObjectDirs, branchUpdate)
forced, err := isForcePush(ctx, rgit, repo.GitUID, env.AlternateObjectDirs, branchUpdate)
if err != nil {
// In case of an error consider this a forced update. In post-update the branch has already been updated,
// so there's less harm in declaring the update as forced.
@ -188,21 +135,15 @@ func (c *Controller) reportBranchEvent(
Msg("failed to check ancestor")
}
payload := &gitevents.BranchUpdatedPayload{
c.gitReporter.BranchUpdated(ctx, &events.BranchUpdatedPayload{
RepoID: repo.ID,
PrincipalID: principalID,
Ref: branchUpdate.Ref,
OldSHA: branchUpdate.Old.String(),
NewSHA: branchUpdate.New.String(),
Forced: forced,
}
c.gitReporter.BranchUpdated(ctx, payload)
c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeBranchUpdated, payload)
})
}
return forced
}
func (c *Controller) reportTagEvent(
@ -213,31 +154,21 @@ func (c *Controller) reportTagEvent(
) {
switch {
case tagUpdate.Old.IsNil():
payload := &gitevents.TagCreatedPayload{
c.gitReporter.TagCreated(ctx, &events.TagCreatedPayload{
RepoID: repo.ID,
PrincipalID: principalID,
Ref: tagUpdate.Ref,
SHA: tagUpdate.New.String(),
}
c.gitReporter.TagCreated(ctx, payload)
c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeTagCreated, payload)
})
case tagUpdate.New.IsNil():
payload := &gitevents.TagDeletedPayload{
c.gitReporter.TagDeleted(ctx, &events.TagDeletedPayload{
RepoID: repo.ID,
PrincipalID: principalID,
Ref: tagUpdate.Ref,
SHA: tagUpdate.Old.String(),
}
c.gitReporter.TagDeleted(ctx, payload)
c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeTagDeleted, payload)
})
default:
payload := &gitevents.TagUpdatedPayload{
c.gitReporter.TagUpdated(ctx, &events.TagUpdatedPayload{
RepoID: repo.ID,
PrincipalID: principalID,
Ref: tagUpdate.Ref,
@ -245,11 +176,7 @@ func (c *Controller) reportTagEvent(
NewSHA: tagUpdate.New.String(),
// tags can only be force updated!
Forced: true,
}
c.gitReporter.TagUpdated(ctx, payload)
c.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeTagUpdated, payload)
})
}
}
@ -257,8 +184,7 @@ func (c *Controller) reportTagEvent(
// TODO: If it is a new branch, or an update on a branch without any PR, it also sends out an SSE for pr creation.
func (c *Controller) handlePRMessaging(
ctx context.Context,
rgit RestrictedGIT,
sourceRepo *types.Repository,
repo *types.Repository,
in hook.PostReceiveInput,
out *hook.Output,
) {
@ -270,33 +196,34 @@ func (c *Controller) handlePRMessaging(
}
// for now we only care about first branch that was pushed.
refUpdate := in.RefUpdates[0]
branchName := in.RefUpdates[0].Ref[len(gitReferenceNamePrefixBranch):]
branchName := refUpdate.Ref[len(gitReferenceNamePrefixBranch):]
newSHA := refUpdate.New
c.suggestPullRequest(ctx, rgit, sourceRepo, branchName, newSHA, out)
c.suggestPullRequest(ctx, repo, branchName, out)
// TODO: store latest pushed branch for user in cache and send out SSE
}
func (c *Controller) suggestPullRequest(
ctx context.Context,
rgit RestrictedGIT,
sourceRepo *types.Repository,
repo *types.Repository,
branchName string,
newSHA sha.SHA,
out *hook.Output,
) {
// Find the most recent few open PRs created from this branch.
if branchName == repo.DefaultBranch {
// Don't suggest a pull request if this is a push to the default branch.
return
}
// do we have a PR related to it?
prs, err := c.pullreqStore.List(ctx, &types.PullReqFilter{
Page: 1,
Size: 10,
SourceRepoID: sourceRepo.ID,
Page: 1,
// without forks we expect at most one PR (keep 2 to not break when forks are introduced)
Size: 2,
SourceRepoID: repo.ID,
SourceBranch: branchName,
// we only care about open PRs - merged/closed will lead to "create new PR" message
States: []enum.PullReqState{enum.PullReqStateOpen},
Order: enum.OrderDesc,
Order: enum.OrderAsc,
Sort: enum.PullReqSortCreated,
// don't care about the PR description, omit it from the response
ExcludeDescription: true,
@ -305,161 +232,30 @@ func (c *Controller) suggestPullRequest(
log.Ctx(ctx).Warn().Err(err).Msgf(
"failed to find pullrequests for branch '%s' originating from repo '%s'",
branchName,
sourceRepo.Path,
repo.Path,
)
return
}
slices.Reverse(prs) // Use ascending order for message output.
// For already existing PRs, check if the merge base is still unique and if there are PR with non-unique merge base
// print them to users terminal to inform about pending closure.
var prsNonUniqueMergeBase []*types.PullReq
for _, pr := range prs {
if pr.SourceRepoID == nil || *pr.SourceRepoID != pr.TargetRepoID {
continue
// for already existing PRs, print them to users terminal for easier access.
if len(prs) > 0 {
msgs := make([]string, 2*len(prs)+1)
msgs[0] = fmt.Sprintf("Branch %q has open PRs:", branchName)
for i, pr := range prs {
msgs[2*i+1] = fmt.Sprintf(" (#%d) %s", pr.Number, pr.Title)
msgs[2*i+2] = " " + c.urlProvider.GenerateUIPRURL(ctx, repo.Path, pr.Number)
}
var targetBranch string
targetBranch, err = git.GetRefPath(pr.TargetBranch, gitenum.RefTypeBranch)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msgf(
"failed to create target reference from target branch'%s' originating from repo '%s'",
pr.TargetBranch,
sourceRepo.Path,
)
continue
}
_, err = rgit.MergeBase(ctx, git.MergeBaseParams{
ReadParams: git.ReadParams{RepoUID: sourceRepo.GitUID},
Ref1: targetBranch,
Ref2: newSHA.String(),
})
if errors.IsInvalidArgument(err) || gitapi.IsUnrelatedHistoriesError(err) {
prsNonUniqueMergeBase = append(prsNonUniqueMergeBase, pr)
continue
}
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msgf(
"failed to find merge base for PR #%d originating from repo '%s'",
pr.Number,
sourceRepo.Path,
)
continue
}
}
msgs, err := c.getNonUniqueMergeBasePRsMessages(ctx, sourceRepo, branchName, prsNonUniqueMergeBase)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("failed to get messages for open pull request")
return
}
if len(msgs) > 0 {
out.Messages = append(out.Messages, msgs...)
return
}
// For already existing PRs, print them to users terminal for easier access.
msgs, err = c.getOpenPRsMessages(ctx, sourceRepo, branchName, prs)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("failed to get messages for open pull request")
return
}
if len(msgs) > 0 {
out.Messages = append(out.Messages, msgs...)
return
}
if branchName == sourceRepo.DefaultBranch {
// Don't suggest a pull request if this is a push to the default branch.
return
}
// This is a new PR!
// this is a new PR!
out.Messages = append(out.Messages,
fmt.Sprintf("Create a pull request for %q by visiting:", branchName),
" "+c.urlProvider.GenerateUICompareURL(ctx, sourceRepo.Path, sourceRepo.DefaultBranch, branchName),
" "+c.urlProvider.GenerateUICompareURL(ctx, repo.Path, repo.DefaultBranch, branchName),
)
}
func (c *Controller) getOpenPRsMessages(
ctx context.Context,
sourceRepo *types.Repository,
branchName string,
prs []*types.PullReq,
) ([]string, error) {
if len(prs) == 0 {
return nil, nil
}
msgs := make([]string, 0, 2*len(prs)+1)
if len(prs) == 1 {
msgs = append(msgs, fmt.Sprintf("Branch %q has an open PR:", branchName))
} else {
msgs = append(msgs, fmt.Sprintf("Branch %q has open PRs:", branchName))
}
msgs, err := c.appendPRs(ctx, prs, sourceRepo, msgs)
if err != nil {
return nil, fmt.Errorf("failed to append PRs: %w", err)
}
return msgs, nil
}
func (c *Controller) getNonUniqueMergeBasePRsMessages(
ctx context.Context,
sourceRepo *types.Repository,
branchName string,
prs []*types.PullReq,
) ([]string, error) {
if len(prs) == 0 {
return nil, nil
}
msgs := make([]string, 0, 2*len(prs)+1)
if len(prs) == 1 {
msgs = append(msgs,
fmt.Sprintf("Branch %q has an open PR that would be closed because non-unique merge base:", branchName))
} else {
msgs = append(msgs,
fmt.Sprintf("Branch %q has open PRs that would be closed because non-unique merge base:", branchName))
}
msgs, err := c.appendPRs(ctx, prs, sourceRepo, msgs)
if err != nil {
return nil, fmt.Errorf("failed to append PRs: %w", err)
}
return msgs, nil
}
func (c *Controller) appendPRs(
ctx context.Context,
prs []*types.PullReq,
sourceRepo *types.Repository,
msgs []string,
) ([]string, error) {
for _, pr := range prs {
path := sourceRepo.Path
if pr.TargetRepoID != *pr.SourceRepoID {
targetRepo, err := c.repoFinder.FindByID(ctx, pr.TargetRepoID)
if err != nil {
return nil, fmt.Errorf("failed to find target repo by ID: %w", err)
}
path = targetRepo.Path
}
msgs = append(msgs, fmt.Sprintf(" (#%d) %s", pr.Number, pr.Title))
msgs = append(msgs, " "+c.urlProvider.GenerateUIPRURL(ctx, path, pr.Number))
}
return msgs, nil
}
// handleEmptyRepoPush updates repo default branch on empty repos if push contains branches.
func (c *Controller) handleEmptyRepoPush(
ctx context.Context,
@ -504,102 +300,12 @@ func (c *Controller) handleEmptyRepoPush(
return
}
c.repoFinder.MarkChanged(ctx, repo.Core())
if repo.DefaultBranch != oldName {
c.repoReporter.DefaultBranchUpdated(ctx, &repoevents.DefaultBranchUpdatedPayload{
Base: repoevents.Base{
RepoID: repo.ID,
PrincipalID: bootstrap.NewSystemServiceSession().Principal.ID,
},
OldName: oldName,
NewName: repo.DefaultBranch,
RepoID: repo.ID,
PrincipalID: bootstrap.NewSystemServiceSession().Principal.ID,
OldName: oldName,
NewName: repo.DefaultBranch,
})
}
}
// updateLastGITPushTime updates the repo's last git push time.
func (c *Controller) updateLastGITPushTime(
ctx context.Context,
repo *types.Repository,
) {
newRepo, err := c.repoStore.UpdateOptLock(ctx, repo, func(r *types.Repository) error {
r.LastGITPush = time.Now().UnixMilli()
return nil
})
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msgf("failed to update last git push time for repo %q", repo.Path)
return
}
*repo = *newRepo
}
// logForcePush detects and logs force pushes to the default branch.
func (c *Controller) logForcePush(
ctx context.Context,
repo *types.Repository,
principalID int64,
refUpdates []hook.ReferenceUpdate,
forcePushStatus refForcePushMap,
) {
if repo.DefaultBranch == "" {
return
}
defaultBranchRef := gitReferenceNamePrefixBranch + repo.DefaultBranch
_, exists := forcePushStatus[defaultBranchRef]
if !exists {
return
}
var defaultBranchUpdate *hook.ReferenceUpdate
for i := range refUpdates {
if refUpdates[i].Ref == defaultBranchRef && !refUpdates[i].New.IsNil() {
defaultBranchUpdate = &refUpdates[i]
break
}
}
if defaultBranchUpdate == nil {
return
}
principal, err := c.principalStore.Find(ctx, principalID)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("failed to find principal who force pushed to default branch")
return
}
err = c.auditService.Log(ctx,
*principal,
audit.NewResource(
audit.ResourceTypeRepository,
repo.Identifier,
audit.RepoPath,
repo.Path,
audit.BypassedResourceType,
audit.BypassedResourceTypeCommit,
audit.ResourceName,
fmt.Sprintf(
audit.BypassSHALabelFormat,
repo.DefaultBranch,
defaultBranchUpdate.New.String()[0:6],
),
),
audit.ActionForcePush,
paths.Parent(repo.Path),
audit.WithOldObject(audit.CommitObject{
CommitSHA: defaultBranchUpdate.Old.String(),
RepoPath: repo.Path,
}),
audit.WithNewObject(audit.CommitObject{
CommitSHA: defaultBranchUpdate.New.String(),
RepoPath: repo.Path,
}),
)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("failed to insert audit log for force push")
}
}

View File

@ -33,9 +33,6 @@ import (
"golang.org/x/exp/slices"
)
// allowedRepoStatesForPush lists repository states that git push is allowed for internal and external calls.
var allowedRepoStatesForPush = []enum.RepoState{enum.RepoStateActive, enum.RepoStateMigrateGitPush}
// PreReceive executes the pre-receive hook for a git repository.
func (c *Controller) PreReceive(
ctx context.Context,
@ -44,29 +41,21 @@ func (c *Controller) PreReceive(
in types.GithookPreReceiveInput,
) (hook.Output, error) {
output := hook.Output{}
defer func() {
logOutputFor(ctx, "pre-receive", output)
}()
repo, err := c.getRepoCheckAccess(ctx, session, in.RepoID, enum.PermissionRepoPush)
if err != nil {
return hook.Output{}, err
}
if !in.Internal && repo.Type == enum.RepoTypeLinked {
output.Error = ptr.String("Push not allowed to a linked repository")
return output, nil
}
if !in.Internal && !slices.Contains(allowedRepoStatesForPush, repo.State) {
output.Error = ptr.String(fmt.Sprintf("Push not allowed when repository is in '%s' state", repo.State))
if !in.Internal && repo.State != enum.RepoStateActive && repo.State != enum.RepoStateMigrateGitPush {
output.Error = ptr.String("Push not allowed in the current repository state")
return output, nil
}
if err := c.limiter.RepoSize(ctx, in.RepoID); err != nil {
return hook.Output{}, fmt.Errorf(
"resource limit exceeded: %w", limiter.ErrMaxRepoSizeReached,
)
"resource limit exceeded: %w",
limiter.ErrMaxRepoSizeReached)
}
forced := make([]bool, len(in.RefUpdates))
@ -93,144 +82,52 @@ func (c *Controller) PreReceive(
return output, nil
}
protectionRules, err := c.protectionManager.ListRepoRules(
ctx, repo.ID, protection.TypeBranch, protection.TypeTag, protection.TypePush,
)
if err != nil {
return hook.Output{}, fmt.Errorf(
"failed to fetch protection rules for the repository: %w", err,
)
}
var principal *types.Principal
repoActive := repo.State == enum.RepoStateActive
if repoActive {
// For internal calls - through the application interface (API) - no need to verify protection rules.
if !in.Internal && repo.State == enum.RepoStateActive {
// TODO: use store.PrincipalInfoCache once we abstracted principals.
principal, err = c.principalStore.Find(ctx, in.PrincipalID)
principal, err := c.principalStore.Find(ctx, in.PrincipalID)
if err != nil {
return hook.Output{}, fmt.Errorf("failed to find inner principal with id %d: %w", in.PrincipalID, err)
}
}
var ruleViolations []types.RuleViolations
var isRepoOwner bool
// For internal calls - through the application interface (API) - no need to verify protection rules.
if !in.Internal && repoActive {
dummySession := &auth.Session{Principal: *principal, Metadata: nil}
isRepoOwner, err = apiauth.IsRepoOwner(ctx, c.authorizer, dummySession, repo)
if err != nil {
return hook.Output{}, fmt.Errorf("failed to determine if user is repo owner: %w", err)
}
ruleViolations, err = c.checkProtectionRules(
ctx, dummySession, repo, refUpdates, protectionRules, isRepoOwner,
)
err = c.checkProtectionRules(ctx, dummySession, repo, refUpdates, &output)
if output.Error != nil {
return output, nil
}
if err != nil {
return hook.Output{}, fmt.Errorf("failed to check protection rules: %w", err)
}
if output.Error != nil {
return output, nil
}
}
err = c.preReceiveExtender.Extend(ctx, rgit, session, repo, in, &output)
if err != nil {
return hook.Output{}, fmt.Errorf("failed to extend pre-receive hook: %w", err)
}
err = c.scanSecrets(ctx, rgit, repo, in, &output)
if output.Error != nil {
return output, nil
}
if err != nil {
return hook.Output{}, err
}
if repoActive {
// check secret scanning apart from push rules as it is enabled in repository settings.
err = c.scanSecrets(ctx, rgit, repo, false, nil, in, &output)
if err != nil {
return hook.Output{}, fmt.Errorf("failed to scan secrets: %w", err)
}
if output.Error != nil {
return output, nil
}
err = c.preReceiveExtender.Extend(ctx, rgit, session, repo, in, &output)
if output.Error != nil {
return output, nil
}
if err != nil {
return hook.Output{}, fmt.Errorf("failed to extend pre-receive hook: %w", err)
}
violations, err := c.processPushProtection(
ctx, rgit, repo, principal, isRepoOwner, refUpdates, protectionRules, in, &output,
)
if err != nil {
return hook.Output{}, err
}
ruleViolations = append(ruleViolations, violations...)
processRuleViolations(&output, ruleViolations)
err = c.checkFileSizeLimit(ctx, rgit, repo, in, &output)
if output.Error != nil {
return output, nil
}
if err != nil {
return hook.Output{}, err
}
return output, nil
}
// processPushProtection handles push protection verification for active repositories.
func (c *Controller) processPushProtection(
ctx context.Context,
rgit RestrictedGIT,
repo *types.RepositoryCore,
principal *types.Principal,
isRepoOwner bool,
refUpdates changedRefs,
protectionRules []types.RuleInfoInternal,
in types.GithookPreReceiveInput,
output *hook.Output,
) ([]types.RuleViolations, error) {
pushProtection := c.protectionManager.FilterCreatePushProtection(protectionRules)
out, _, err := pushProtection.PushVerify(
ctx,
protection.PushVerifyInput{
ResolveUserGroupID: c.userGroupService.ListUserIDsByGroupIDs,
Actor: principal,
IsRepoOwner: isRepoOwner,
RepoID: repo.ID,
RepoIdentifier: repo.Identifier,
},
)
if err != nil {
return nil, fmt.Errorf("failed to verify git objects: %w", err)
}
if len(out.Protections) == 0 {
// No push protections to verify.
return []types.RuleViolations{}, nil
}
violationsInput := &protection.PushViolationsInput{
ResolveUserGroupID: c.userGroupService.ListUserIDsByGroupIDs,
Actor: principal,
IsRepoOwner: isRepoOwner,
Protections: out.Protections,
}
err = c.scanSecrets(ctx, rgit, repo, out.SecretScanningEnabled, violationsInput, in, output)
if err != nil {
return nil, fmt.Errorf("failed to scan secrets: %w", err)
}
if err = c.processObjects(
ctx, rgit,
repo, principal, refUpdates,
out.FileSizeLimit, out.PrincipalCommitterMatch, violationsInput,
in, output,
); err != nil {
return nil, fmt.Errorf("failed to process pre-receive objects: %w", err)
}
var violations []types.RuleViolations
if violationsInput.HasViolations() {
pushViolations, err := pushProtection.Violations(ctx, violationsInput)
if err != nil {
return nil, fmt.Errorf("failed to backfill violations: %w", err)
}
violations = pushViolations.Violations
}
return violations, nil
}
func (c *Controller) blockPullReqRefUpdate(refUpdates changedRefs, state enum.RepoState) bool {
if state == enum.RepoStateMigrateGitPush {
return false
@ -249,37 +146,37 @@ func (c *Controller) blockPullReqRefUpdate(refUpdates changedRefs, state enum.Re
func (c *Controller) checkProtectionRules(
ctx context.Context,
session *auth.Session,
repo *types.RepositoryCore,
repo *types.Repository,
refUpdates changedRefs,
protectionRules []types.RuleInfoInternal,
isRepoOwner bool,
) ([]types.RuleViolations, error) {
branchProtection := c.protectionManager.FilterCreateBranchProtection(protectionRules)
tagProtection := c.protectionManager.FilterCreateTagProtection(protectionRules)
output *hook.Output,
) error {
isRepoOwner, err := apiauth.IsRepoOwner(ctx, c.authorizer, session, repo)
if err != nil {
return fmt.Errorf("failed to determine if user is repo owner: %w", err)
}
protectionRules, err := c.protectionManager.ForRepository(ctx, repo.ID)
if err != nil {
return fmt.Errorf("failed to fetch protection rules for the repository: %w", err)
}
var ruleViolations []types.RuleViolations
var errCheckAction error
//nolint:unparam
checkAction := func(
refProtection protection.RefProtection,
refAction protection.RefAction,
refType protection.RefType,
names []string,
) {
checkAction := func(refAction protection.RefAction, refType protection.RefType, names []string) {
if errCheckAction != nil || len(names) == 0 {
return
}
violations, err := refProtection.RefChangeVerify(ctx, protection.RefChangeVerifyInput{
ResolveUserGroupID: c.userGroupService.ListUserIDsByGroupIDs,
Actor: &session.Principal,
AllowBypass: true,
IsRepoOwner: isRepoOwner,
Repo: repo,
RefAction: refAction,
RefType: refType,
RefNames: names,
violations, err := protectionRules.RefChangeVerify(ctx, protection.RefChangeVerifyInput{
Actor: &session.Principal,
AllowBypass: true,
IsRepoOwner: isRepoOwner,
Repo: repo,
RefAction: refAction,
RefType: refType,
RefNames: names,
})
if err != nil {
errCheckAction = fmt.Errorf("failed to verify protection rules for git push: %w", err)
@ -289,49 +186,13 @@ func (c *Controller) checkProtectionRules(
ruleViolations = append(ruleViolations, violations...)
}
checkAction(
branchProtection, protection.RefActionCreate,
protection.RefTypeBranch, refUpdates.branches.created,
)
checkAction(
branchProtection, protection.RefActionDelete,
protection.RefTypeBranch, refUpdates.branches.deleted,
)
checkAction(
branchProtection, protection.RefActionUpdate,
protection.RefTypeBranch, refUpdates.branches.updated,
)
checkAction(
branchProtection, protection.RefActionUpdateForce,
protection.RefTypeBranch, refUpdates.branches.forced,
)
checkAction(
tagProtection, protection.RefActionCreate,
protection.RefTypeTag, refUpdates.tags.created,
)
checkAction(
tagProtection, protection.RefActionDelete,
protection.RefTypeTag, refUpdates.tags.deleted,
)
checkAction(
tagProtection, protection.RefActionUpdateForce,
protection.RefTypeTag, refUpdates.tags.forced,
)
checkAction(protection.RefActionCreate, protection.RefTypeBranch, refUpdates.branches.created)
checkAction(protection.RefActionDelete, protection.RefTypeBranch, refUpdates.branches.deleted)
checkAction(protection.RefActionUpdate, protection.RefTypeBranch, refUpdates.branches.updated)
checkAction(protection.RefActionUpdateForce, protection.RefTypeBranch, refUpdates.branches.forced)
if errCheckAction != nil {
return nil, errCheckAction
}
return ruleViolations, nil
}
func processRuleViolations(
output *hook.Output,
ruleViolations []types.RuleViolations,
) {
if len(ruleViolations) == 0 {
return
return errCheckAction
}
var criticalViolation bool
@ -352,6 +213,8 @@ func processRuleViolations(
if criticalViolation {
output.Error = ptr.String("Blocked by protection rules.")
}
return nil
}
type changes struct {
@ -384,30 +247,15 @@ type changedRefs struct {
other changes
}
func (c *changedRefs) hasOnlyDeletedBranches() bool {
if len(c.branches.created) > 0 || len(c.branches.updated) > 0 || len(c.branches.forced) > 0 {
return false
}
return true
}
func isBranch(ref string) bool {
return strings.HasPrefix(ref, gitReferenceNamePrefixBranch)
}
func isTag(ref string) bool {
return strings.HasPrefix(ref, gitReferenceNamePrefixTag)
}
func groupRefsByAction(refUpdates []hook.ReferenceUpdate, forced []bool) (c changedRefs) {
for i, refUpdate := range refUpdates {
switch {
case isBranch(refUpdate.Ref):
case strings.HasPrefix(refUpdate.Ref, gitReferenceNamePrefixBranch):
branchName := refUpdate.Ref[len(gitReferenceNamePrefixBranch):]
c.branches.groupByAction(refUpdate, branchName, forced[i])
case isTag(refUpdate.Ref):
case strings.HasPrefix(refUpdate.Ref, gitReferenceNamePrefixTag):
tagName := refUpdate.Ref[len(gitReferenceNamePrefixTag):]
c.tags.groupByAction(refUpdate, tagName, forced[i])
c.tags.groupByAction(refUpdate, tagName, false)
default:
c.other.groupByAction(refUpdate, refUpdate.Ref, false)
}

View File

@ -0,0 +1,81 @@
// 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 githook
import (
"context"
"fmt"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/hook"
"github.com/harness/gitness/types"
"github.com/gotidy/ptr"
)
func (c *Controller) checkFileSizeLimit(
ctx context.Context,
rgit RestrictedGIT,
repo *types.Repository,
in types.GithookPreReceiveInput,
output *hook.Output,
) error {
// return if all new refs are nil refs
allNilRefs := true
for _, refUpdate := range in.RefUpdates {
if refUpdate.New.IsNil() {
continue
}
allNilRefs = false
break
}
if allNilRefs {
return nil
}
sizeLimit, err := settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeyFileSizeLimit,
settings.DefaultFileSizeLimit,
)
if err != nil {
return fmt.Errorf("failed to check settings for file size limit: %w", err)
}
if sizeLimit <= 0 {
return nil
}
res, err := rgit.FindOversizeFiles(
ctx,
&git.FindOversizeFilesParams{
RepoUID: repo.GitUID,
GitObjectDirs: in.Environment.AlternateObjectDirs,
SizeLimit: sizeLimit,
},
)
if err != nil {
return fmt.Errorf("failed to get file sizes: %w", err)
}
if len(res.FileInfos) > 0 {
output.Error = ptr.String("Changes blocked by files exceeding the file size limit")
printOversizeFiles(output, res.FileInfos, sizeLimit)
}
return nil
}

View File

@ -1,174 +0,0 @@
// 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 githook
import (
"context"
"fmt"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/hook"
"github.com/harness/gitness/types"
"github.com/gotidy/ptr"
)
func (c *Controller) processObjects(
ctx context.Context,
rgit RestrictedGIT,
repo *types.RepositoryCore,
principal *types.Principal,
refUpdates changedRefs,
sizeLimit int64,
principalCommitterMatch bool,
violationsInput *protection.PushViolationsInput,
in types.GithookPreReceiveInput,
output *hook.Output,
) error {
if refUpdates.hasOnlyDeletedBranches() {
return nil
}
// TODO: Remove this once push rules implementation and migration are complete.
settingsSizeLimit, err := settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeyFileSizeLimit,
settings.DefaultFileSizeLimit,
)
if err != nil {
return fmt.Errorf("failed to check settings for file size limit: %w", err)
}
if sizeLimit == 0 || (settingsSizeLimit > 0 && sizeLimit > settingsSizeLimit) {
sizeLimit = settingsSizeLimit
}
// TODO: Remove this once push rules implementation and migration are complete.
if !principalCommitterMatch {
principalCommitterMatch, err = settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeyPrincipalCommitterMatch,
settings.DefaultPrincipalCommitterMatch,
)
if err != nil {
return fmt.Errorf("failed to check settings for principal committer match: %w", err)
}
}
gitLFSEnabled, err := settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeyGitLFSEnabled,
settings.DefaultGitLFSEnabled,
)
if err != nil {
return fmt.Errorf("failed to check settings for Git LFS enabled: %w", err)
}
if sizeLimit == 0 && !principalCommitterMatch && !gitLFSEnabled {
return nil
}
preReceiveObjsIn := git.ProcessPreReceiveObjectsParams{
ReadParams: git.ReadParams{
RepoUID: repo.GitUID,
AlternateObjectDirs: in.Environment.AlternateObjectDirs,
},
}
if sizeLimit > 0 {
preReceiveObjsIn.FindOversizeFilesParams = &git.FindOversizeFilesParams{
SizeLimit: sizeLimit,
}
}
if principalCommitterMatch && principal != nil && !in.Internal {
preReceiveObjsIn.FindCommitterMismatchParams = &git.FindCommitterMismatchParams{
PrincipalEmail: principal.Email,
}
}
if gitLFSEnabled {
preReceiveObjsIn.FindLFSPointersParams = &git.FindLFSPointersParams{}
}
preReceiveObjsOut, err := rgit.ProcessPreReceiveObjects(
ctx,
preReceiveObjsIn,
)
if err != nil {
return fmt.Errorf("failed to process pre-receive objects: %w", err)
}
if preReceiveObjsOut.FindOversizeFilesOutput != nil &&
len(preReceiveObjsOut.FindOversizeFilesOutput.FileInfos) > 0 {
printOversizeFiles(
output,
preReceiveObjsOut.FindOversizeFilesOutput.FileInfos,
preReceiveObjsOut.FindOversizeFilesOutput.Total,
sizeLimit,
)
}
if preReceiveObjsOut.FindCommitterMismatchOutput != nil &&
len(preReceiveObjsOut.FindCommitterMismatchOutput.CommitInfos) > 0 {
printCommitterMismatch(
output,
preReceiveObjsOut.FindCommitterMismatchOutput.CommitInfos,
preReceiveObjsIn.FindCommitterMismatchParams.PrincipalEmail,
preReceiveObjsOut.FindCommitterMismatchOutput.Total,
)
}
if preReceiveObjsOut.FindLFSPointersOutput != nil &&
len(preReceiveObjsOut.FindLFSPointersOutput.LFSInfos) > 0 {
objIDs := make([]string, len(preReceiveObjsOut.FindLFSPointersOutput.LFSInfos))
for i, info := range preReceiveObjsOut.FindLFSPointersOutput.LFSInfos {
objIDs[i] = info.ObjID
}
existingObjs, err := c.lfsStore.FindMany(ctx, in.RepoID, objIDs)
if err != nil {
return fmt.Errorf("failed to find lfs objects: %w", err)
}
//nolint:lll
if len(existingObjs) != len(objIDs) {
output.Error = ptr.String(
"Changes blocked by unknown Git LFS objects. Please try `git lfs push --all` or check if LFS is setup properly.")
printLFSPointers(
output,
preReceiveObjsOut.FindLFSPointersOutput.LFSInfos,
preReceiveObjsOut.FindLFSPointersOutput.Total,
)
}
}
violationsInput.FileSizeLimit = sizeLimit
violationsInput.FindOversizeFilesOutput = preReceiveObjsOut.FindOversizeFilesOutput
violationsInput.PrincipalCommitterMatch = principalCommitterMatch
if preReceiveObjsOut.FindCommitterMismatchOutput != nil {
violationsInput.CommitterMismatchCount = preReceiveObjsOut.FindCommitterMismatchOutput.Total
}
return nil
}

View File

@ -19,7 +19,6 @@ import (
"fmt"
"time"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/hook"
@ -38,26 +37,21 @@ type secretFinding struct {
func (c *Controller) scanSecrets(
ctx context.Context,
rgit RestrictedGIT,
repo *types.RepositoryCore,
scanningEnabled bool,
violationsInput *protection.PushViolationsInput,
repo *types.Repository,
in types.GithookPreReceiveInput,
output *hook.Output,
) error {
if !scanningEnabled {
var err error
scanningEnabled, err = settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeySecretScanningEnabled,
settings.DefaultSecretScanningEnabled,
)
if err != nil {
return fmt.Errorf("failed to check settings whether secret scanning is enabled: %w", err)
}
// check if scanning is enabled on the repo
scanningEnabled, err := settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeySecretScanningEnabled,
settings.DefaultSecretScanningEnabled,
)
if err != nil {
return fmt.Errorf("failed to check settings whether secret scanning is enabled: %w", err)
}
if !scanningEnabled {
return nil
}
@ -77,15 +71,9 @@ func (c *Controller) scanSecrets(
// always print result (handles both no results and results found)
printScanSecretsFindings(output, findings, len(in.RefUpdates) > 1, time.Since(startTime))
// this will be removed when secret scanning check will be moved to push protection
if len(findings) > 0 && violationsInput == nil {
errMsg := fmt.Sprintf("Found %d secret(s) in your code. Push rejected.", len(findings))
output.Error = ptr.String(errMsg)
}
if violationsInput != nil {
violationsInput.SecretScanningEnabled = scanningEnabled
violationsInput.FoundSecretCount = len(findings)
// block the push if any secrets were found
if len(findings) > 0 {
output.Error = ptr.String("Changes blocked by security scan results")
}
return nil
@ -93,7 +81,7 @@ func (c *Controller) scanSecrets(
func scanSecretsInternal(ctx context.Context,
rgit RestrictedGIT,
repo *types.RepositoryCore,
repo *types.Repository,
in types.GithookPreReceiveInput,
) ([]secretFinding, error) {
var baseRevFallBack *string

View File

@ -107,7 +107,6 @@ func FMTDuration(d time.Duration) string {
func printOversizeFiles(
output *hook.Output,
oversizeFiles []git.FileInfo,
total int64,
sizeLimit int64,
) {
output.Messages = append(
@ -127,6 +126,7 @@ func printOversizeFiles(
)
}
total := len(oversizeFiles)
output.Messages = append(
output.Messages,
colorScanSummary.Sprintf(
@ -137,71 +137,6 @@ func printOversizeFiles(
)
}
func printCommitterMismatch(
output *hook.Output,
commitInfos []git.CommitInfo,
principalEmail string,
total int64,
) {
output.Messages = append(
output.Messages,
colorScanHeader.Sprintf(
"Push contains commits where committer is not the authenticated user (%s):",
principalEmail,
),
"", // add empty line for making it visually more consumable
)
for _, info := range commitInfos {
output.Messages = append(
output.Messages,
fmt.Sprintf(" %s Committer: %s", info.SHA, info.Committer),
"", // add empty line for making it visually more consumable
)
}
output.Messages = append(
output.Messages,
colorScanSummary.Sprintf(
"%d %s found not matching the authenticated user (%s)",
total, singularOrPlural("commit", total > 1), principalEmail,
),
"", "", // add two empty lines for making it visually more consumable
)
}
func printLFSPointers(
output *hook.Output,
lfsInfos []git.LFSInfo,
total int64,
) {
output.Messages = append(
output.Messages,
colorScanHeader.Sprintf(
"Push references unknown LFS objects:",
),
"", // add empty line for making it visually more consumable
)
for _, info := range lfsInfos {
output.Messages = append(
output.Messages,
fmt.Sprintf(" Object ID: %s", info.ObjID),
fmt.Sprintf(" File SHA : %s", info.SHA),
"", // add empty line for making it visually more consumable
)
}
output.Messages = append(
output.Messages,
colorScanSummary.Sprintf(
"%d %s missing",
total, singularOrPlural("LFS object", total > 1),
),
"", "", // add two empty lines for making it visually more consumable
)
}
func singularOrPlural(noun string, plural bool) string {
if plural {
return noun + "s"

View File

@ -20,13 +20,9 @@ import (
eventsgit "github.com/harness/gitness/app/events/git"
eventsrepo "github.com/harness/gitness/app/events/repo"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/services/usergroup"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/hook"
@ -40,9 +36,7 @@ var WireSet = wire.NewSet(
func ProvideFactory() hook.ClientFactory {
return &ControllerClientFactory{
// fields are set in ProvideController to avoid import
githookCtrl: nil,
git: nil,
}
}
@ -50,7 +44,6 @@ func ProvideController(
authorizer authz.Authorizer,
principalStore store.PrincipalStore,
repoStore store.RepoStore,
repoFinder refcache.RepoFinder,
gitReporter *eventsgit.Reporter,
repoReporter *eventsrepo.Reporter,
git git.Interface,
@ -63,18 +56,14 @@ func ProvideController(
preReceiveExtender PreReceiveExtender,
updateExtender UpdateExtender,
postReceiveExtender PostReceiveExtender,
sseStreamer sse.Streamer,
lfsStore store.LFSObjectStore,
auditService audit.Service,
userGroupService usergroup.Service,
) *Controller {
ctrl := NewController(
authorizer,
principalStore,
repoStore,
repoFinder,
gitReporter,
repoReporter,
git,
pullreqStore,
urlProvider,
protectionManager,
@ -83,10 +72,6 @@ func ProvideController(
preReceiveExtender,
updateExtender,
postReceiveExtender,
sseStreamer,
lfsStore,
auditService,
userGroupService,
)
// TODO: improve wiring if possible

View File

@ -42,25 +42,28 @@ func (c *Controller) Action(
if err := c.sanitizeActionInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
space, err := c.spaceFinder.FindByRef(ctx, in.SpaceRef)
space, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, in.Identifier, enum.PermissionGitspaceUse)
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, in.Identifier, enum.PermissionGitspaceAccess)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
gitspaceConfig, err := c.gitspaceSvc.FindWithLatestInstance(ctx, space.ID, in.Identifier)
gitspaceConfig, err := c.gitspaceConfigStore.FindByIdentifier(ctx, space.ID, in.Identifier)
gitspaceConfig.SpacePath = space.Path
gitspaceConfig.SpaceID = space.ID
if err != nil {
return nil, fmt.Errorf("failed to find gitspace config: %w", err)
}
// check if it's an internal repo
if gitspaceConfig.CodeRepo.Type == enum.CodeRepoTypeGitness {
if gitspaceConfig.CodeRepo.Ref == nil {
return nil, fmt.Errorf("couldn't fetch repo for the user, no ref found: %w", err)
}
repo, err := c.repoFinder.FindByRef(ctx, *gitspaceConfig.CodeRepo.Ref)
repo, err := c.repoStore.FindByRef(ctx, *gitspaceConfig.CodeRepo.Ref)
if err != nil {
return nil, fmt.Errorf("couldn't fetch repo for the user: %w", err)
}
@ -79,27 +82,17 @@ func (c *Controller) Action(
// All the actions should be idempotent.
switch in.Action {
case enum.GitspaceActionTypeStart:
err = c.gitspaceLimiter.Usage(ctx, space.ID, gitspaceConfig.InfraProviderResource.InfraProviderType)
err = c.gitspaceLimiter.Usage(ctx, space.ID)
if err != nil {
return nil, err
}
c.gitspaceSvc.EmitGitspaceConfigEvent(ctx, *gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStart)
if err = c.gitspaceSvc.StartGitspaceAction(ctx, *gitspaceConfig); err == nil {
gitspaceConfig.State = enum.GitspaceStateStarting
}
c.gitspaceSvc.EmitGitspaceConfigEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStart)
err = c.gitspaceSvc.StartGitspaceAction(ctx, gitspaceConfig)
return gitspaceConfig, err
case enum.GitspaceActionTypeStop:
c.gitspaceSvc.EmitGitspaceConfigEvent(ctx, *gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStop)
if err = c.gitspaceSvc.StopGitspaceAction(ctx, *gitspaceConfig, time.Now()); err == nil {
gitspaceConfig.State = enum.GitspaceStateStopping
}
return gitspaceConfig, err
case enum.GitspaceActionTypeReset:
c.gitspaceSvc.EmitGitspaceConfigEvent(ctx, *gitspaceConfig, enum.GitspaceEventTypeGitspaceActionReset)
if err = c.gitspaceSvc.ResetGitspaceAction(ctx, *gitspaceConfig); err == nil {
gitspaceConfig.State = enum.GitSpaceStateCleaning
}
c.gitspaceSvc.EmitGitspaceConfigEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStop)
err = c.gitspaceSvc.StopGitspaceAction(ctx, gitspaceConfig, time.Now())
return gitspaceConfig, err
default:
return nil, fmt.Errorf("unknown action %s on gitspace : %s", string(in.Action), gitspaceConfig.Identifier)

View File

@ -1,225 +0,0 @@
// 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 common
import (
"context"
"fmt"
"strconv"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
// FilterResourcesByCompatibility filters resources based on compatibility with a reference resource.
// It removes any resources that are not compatible according to the IsResourceSpecChangeAllowed criteria.
func FilterResourcesByCompatibility(
ctx context.Context,
filteredResources []*types.InfraProviderResource,
referenceResource *types.InfraProviderResource,
) ([]*types.InfraProviderResource, error) {
if referenceResource == nil {
return nil, fmt.Errorf("referenceResource cannot be nil")
}
compatibleResources := make([]*types.InfraProviderResource, 0)
// Now filter based on compatibility
for _, resource := range filteredResources {
// Skip the current resource itself
if resource.UID == referenceResource.UID {
continue
}
_, err := IsResourceSpecChangeAllowed(referenceResource, resource)
if err != nil {
log.Ctx(ctx).Debug().
Err(err).
Str("resource_id", resource.UID).
Str("reference_id", referenceResource.UID).
Msg("resource compatibility check failed")
} else {
compatibleResources = append(compatibleResources, resource)
}
}
return compatibleResources, nil
}
// IsResourceSpecChangeAllowed checks if the new resource specs are valid and determines if a hard reset is needed.
// Returns (markForHardReset, error) where error contains details about why the validation failed.
func IsResourceSpecChangeAllowed(
existingResource *types.InfraProviderResource,
newResource *types.InfraProviderResource,
) (bool, error) {
// If either resource is nil, we can't compare properly
if existingResource == nil || newResource == nil {
return false, fmt.Errorf("cannot validate resource change: missing resource information")
}
// Validate region is the same
if existingResource.Region != newResource.Region {
return false, usererror.BadRequestf(
"region mismatch: current region '%s' does not match target region '%s'",
existingResource.Region, newResource.Region)
}
// Check zone from metadata if available
existingZone, existingHasZone := existingResource.Metadata["zone"]
newZone, newHasZone := newResource.Metadata["zone"]
// If both resources have zone info, they must match
if existingHasZone && newHasZone && existingZone != newZone {
return false, usererror.BadRequestf(
"zone mismatch: current zone '%s' does not match target zone '%s'",
existingZone, newZone,
)
}
markForInfraReset := false
// Check boot disk changes
needsHardReset, err := validateBootDiskChanges(existingResource.Metadata, newResource.Metadata)
if err != nil {
return false, err
}
if needsHardReset {
markForInfraReset = true
}
// Check persistent disk changes
needsHardReset, err = validatePersistentDiskChanges(existingResource.Metadata, newResource.Metadata)
if err != nil {
return false, err
}
if needsHardReset {
markForInfraReset = true
}
// Check machine type changes
machineTypeResetNeeded := validateMachineTypeChanges(existingResource.Metadata, newResource.Metadata)
markForInfraReset = markForInfraReset || machineTypeResetNeeded
// All checks passed
return markForInfraReset, nil
}
// validatePersistentDiskChanges checks if persistent disk changes are valid and if they require a hard reset.
// Returns (needsHardReset, error).
func validatePersistentDiskChanges(existingMeta, newMeta map[string]string) (bool, error) {
existingDisk, existingOK := existingMeta["persistent_disk_size"]
newDisk, newOK := newMeta["persistent_disk_size"]
if !existingOK || !newOK {
return false, fmt.Errorf(
"invalid persistent disk size format: cannot parse persistent disk sizes for comparison")
}
markForHardReset, err := checkPersistentDiskSizeChange(existingDisk, newDisk)
if err != nil {
return false, err
}
existingDiskType, existingOK := existingMeta["persistent_disk_type"]
newDiskType, newOK := newMeta["persistent_disk_type"]
if !existingOK || !newOK {
return false, fmt.Errorf(
"invalid persistent disk type format: cannot parse persistent disk types for comparison")
}
if existingDiskType != newDiskType {
return false, usererror.BadRequestf(
"persistent disk type change not allowed: from '%s' to '%s'",
existingDiskType, newDiskType)
}
return markForHardReset, nil
}
// validateMachineTypeChanges checks if machine type changes require a hard reset.
// Returns needsHardReset.
func validateMachineTypeChanges(existingMeta, newMeta map[string]string) bool {
existingMachine, existingOK := existingMeta["machine_type"]
newMachine, newOK := newMeta["machine_type"]
if existingOK && newOK && existingMachine != newMachine {
return true
}
return false
}
// validateBootDiskChanges checks if boot disk changes are valid and if they require a hard reset.
// Returns (needsHardReset, error).
func validateBootDiskChanges(existingMeta, newMeta map[string]string) (bool, error) {
markForHardReset := false
// Check boot disk size changes
existingBoot, existingOK := existingMeta["boot_disk_size"]
newBoot, newOK := newMeta["boot_disk_size"]
if !existingOK || !newOK {
return false, fmt.Errorf(
"invalid boot disk size format: cannot parse boot disk sizes for comparison")
}
existingVal, eErr := strconv.Atoi(existingBoot)
newVal, nErr := strconv.Atoi(newBoot)
if eErr != nil || nErr != nil {
return false, fmt.Errorf(
"invalid boot disk size format: cannot parse boot disk sizes for comparison")
}
if newVal != existingVal {
markForHardReset = true
}
// Check boot disk type changes
existingBootType, existingOK := existingMeta["boot_disk_type"]
newBootType, newOK := newMeta["boot_disk_type"]
if !existingOK || !newOK {
return false, fmt.Errorf(
"invalid boot disk type format: cannot parse boot disk types for comparison")
}
if existingBootType != newBootType {
markForHardReset = true
}
return markForHardReset, nil
}
// checkPersistentDiskSizeChange compares existing and new persistent disk sizes.
// and determines if the change is allowed and if hard reset is needed.
// Returns (needsHardReset, error).
//
//nolint:unparam // the bool return value is kept for future extension
func checkPersistentDiskSizeChange(existingDisk, newDisk string) (bool, error) {
existingVal, eErr := strconv.Atoi(existingDisk)
if eErr != nil {
return false, fmt.Errorf("invalid disk size format: cannot parse existing disk size: %w", eErr)
}
newVal, nErr := strconv.Atoi(newDisk)
if nErr != nil {
return false, fmt.Errorf("invalid disk size format: cannot parse new disk size: %w", nErr)
}
// Disallow any changes to persistent disk size
if newVal != existingVal {
return false, fmt.Errorf(
"changing persistent disk size is not allowed: from %d to %d",
existingVal, newVal)
}
// Equal sizes, no hard reset needed
return false, nil
}

View File

@ -20,54 +20,55 @@ import (
"github.com/harness/gitness/app/gitspace/logutil"
"github.com/harness/gitness/app/gitspace/scm"
"github.com/harness/gitness/app/services/gitspace"
"github.com/harness/gitness/app/services/gitspacesettings"
"github.com/harness/gitness/app/services/infraprovider"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
)
// gitspaceInstanceCleaningTimedOutMins is timeout for which a gitspace instance can be in cleaning state.
const gitspaceInstanceCleaningTimedOutMins = 10
type Controller struct {
authorizer authz.Authorizer
infraProviderSvc *infraprovider.Service
spaceStore store.SpaceStore
spaceFinder refcache.SpaceFinder
gitspaceEventStore store.GitspaceEventStore
tx dbtx.Transactor
statefulLogger *logutil.StatefulLogger
scm *scm.SCM
gitspaceSvc *gitspace.Service
gitspaceLimiter limiter.Gitspace
repoFinder refcache.RepoFinder
settingsService gitspacesettings.Service
authorizer authz.Authorizer
infraProviderSvc *infraprovider.Service
gitspaceConfigStore store.GitspaceConfigStore
gitspaceInstanceStore store.GitspaceInstanceStore
spaceStore store.SpaceStore
gitspaceEventStore store.GitspaceEventStore
tx dbtx.Transactor
statefulLogger *logutil.StatefulLogger
scm *scm.SCM
repoStore store.RepoStore
gitspaceSvc *gitspace.Service
gitspaceLimiter limiter.Gitspace
}
func NewController(
tx dbtx.Transactor,
authorizer authz.Authorizer,
infraProviderSvc *infraprovider.Service,
gitspaceConfigStore store.GitspaceConfigStore,
gitspaceInstanceStore store.GitspaceInstanceStore,
spaceStore store.SpaceStore,
spaceFinder refcache.SpaceFinder,
gitspaceEventStore store.GitspaceEventStore,
statefulLogger *logutil.StatefulLogger,
scm *scm.SCM,
repoStore store.RepoStore,
gitspaceSvc *gitspace.Service,
gitspaceLimiter limiter.Gitspace,
repoFinder refcache.RepoFinder,
settingsService gitspacesettings.Service,
) *Controller {
return &Controller{
tx: tx,
authorizer: authorizer,
infraProviderSvc: infraProviderSvc,
spaceStore: spaceStore,
spaceFinder: spaceFinder,
gitspaceEventStore: gitspaceEventStore,
statefulLogger: statefulLogger,
scm: scm,
gitspaceSvc: gitspaceSvc,
gitspaceLimiter: gitspaceLimiter,
repoFinder: repoFinder,
settingsService: settingsService,
tx: tx,
authorizer: authorizer,
infraProviderSvc: infraProviderSvc,
gitspaceConfigStore: gitspaceConfigStore,
gitspaceInstanceStore: gitspaceInstanceStore,
spaceStore: spaceStore,
gitspaceEventStore: gitspaceEventStore,
statefulLogger: statefulLogger,
scm: scm,
repoStore: repoStore,
gitspaceSvc: gitspaceSvc,
gitspaceLimiter: gitspaceLimiter,
}
}

View File

@ -17,7 +17,6 @@ package gitspace
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
@ -25,7 +24,6 @@ import (
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/app/paths"
"github.com/harness/gitness/app/services/gitspace"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/store"
@ -36,35 +34,29 @@ import (
gonanoid "github.com/matoous/go-nanoid"
)
const (
defaultResourceIdentifier = "default"
maxGitspaceConfigIdentifierPrefixLength = 50
suffixLen = 6
)
const defaultResourceIdentifier = "default"
var (
// ErrGitspaceRequiresParent if the user tries to create a secret without a parent space.
// errSecretRequiresParent if the user tries to create a secret without a parent space.
ErrGitspaceRequiresParent = usererror.BadRequest(
"Parent space required - standalone gitspace are not supported.")
)
// CreateInput is the input used for create operations.
type CreateInput struct {
Identifier string `json:"identifier"`
Name string `json:"name"`
SpaceRef string `json:"space_ref"` // Ref of the parent space
IDE enum.IDEType `json:"ide"`
InfraProviderConfigIdentifier string `json:"infra_provider_config_identifier"`
ResourceIdentifier string `json:"resource_identifier"`
ResourceSpaceRef string `json:"resource_space_ref"`
CodeRepoURL string `json:"code_repo_url"`
CodeRepoType enum.GitspaceCodeRepoType `json:"code_repo_type"`
CodeRepoRef *string `json:"code_repo_ref"`
Branch string `json:"branch"`
DevcontainerPath *string `json:"devcontainer_path"`
Metadata map[string]string `json:"metadata"`
SSHTokenIdentifier string `json:"ssh_token_identifier"`
AIAgents []enum.AIAgent `json:"ai_agents"`
Identifier string `json:"identifier"`
Name string `json:"name"`
SpaceRef string `json:"space_ref"` // Ref of the parent space
IDE enum.IDEType `json:"ide"`
ResourceIdentifier string `json:"resource_identifier"`
ResourceSpaceRef string `json:"resource_space_ref"`
CodeRepoURL string `json:"code_repo_url"`
CodeRepoType enum.GitspaceCodeRepoType `json:"code_repo_type"`
CodeRepoRef *string `json:"code_repo_ref"`
Branch string `json:"branch"`
DevcontainerPath *string `json:"devcontainer_path"`
Metadata map[string]string `json:"metadata"`
SSHTokenIdentifier string `json:"ssh_token_identifier"`
}
// Create creates a new gitspace.
@ -73,7 +65,7 @@ func (c *Controller) Create(
session *auth.Session,
in *CreateInput,
) (*types.GitspaceConfig, error) {
space, err := c.spaceFinder.FindByRef(ctx, in.SpaceRef)
space, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
@ -86,13 +78,18 @@ func (c *Controller) Create(
session,
space.Path,
"",
enum.PermissionGitspaceCreate); err != nil {
enum.PermissionGitspaceEdit); err != nil {
return nil, err
}
err = c.gitspaceLimiter.Usage(ctx, space.ID)
if err != nil {
return nil, err
}
// check if it's an internal repo
if in.CodeRepoType == enum.CodeRepoTypeGitness && *in.CodeRepoRef != "" {
repo, err := c.repoFinder.FindByRef(ctx, *in.CodeRepoRef)
repo, err := c.repoStore.FindByRef(ctx, *in.CodeRepoRef)
if err != nil {
return nil, fmt.Errorf("couldn't fetch repo for the user: %w", err)
}
@ -105,22 +102,19 @@ func (c *Controller) Create(
return nil, err
}
}
identifier, err := buildIdentifier(in.Identifier)
suffixUID, err := gonanoid.Generate(gitspace.AllowedUIDAlphabet, 6)
if err != nil {
return nil, fmt.Errorf("could not generate identifier for gitspace config : %q %w", in.Identifier, err)
return nil, fmt.Errorf("could not generate UID for gitspace config : %q %w", in.Identifier, err)
}
identifier := strings.ToLower(in.Identifier + "-" + suffixUID)
now := time.Now().UnixMilli()
var gitspaceConfig *types.GitspaceConfig
resourceIdentifier := in.ResourceIdentifier
// assume resource to be in same space if it's not explicitly specified.
if in.ResourceSpaceRef == "" {
rootSpaceRef, _, err := paths.DisectRoot(in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("unable to find root space path for %s: %w", in.SpaceRef, err)
}
in.ResourceSpaceRef = rootSpaceRef
in.ResourceSpaceRef = in.SpaceRef
}
resourceIdentifier := in.ResourceIdentifier
resourceSpace, err := c.spaceFinder.FindByRef(ctx, in.ResourceSpaceRef)
resourceSpace, err := c.spaceStore.FindByRef(ctx, in.ResourceSpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
@ -129,29 +123,22 @@ func (c *Controller) Create(
c.authorizer,
session,
resourceSpace.Path,
"",
enum.PermissionInfraProviderView); err != nil {
resourceIdentifier,
enum.PermissionInfraProviderAccess); err != nil {
return nil, err
}
// TODO: Temp fix to ensure the gitspace creation doesnt fail. Once the FE starts sending this field in the
// request, remove this.
if in.InfraProviderConfigIdentifier == "" {
in.InfraProviderConfigIdentifier = defaultResourceIdentifier
}
infraProviderResource, err := c.createOrFindInfraProviderResource(ctx, resourceSpace, resourceIdentifier,
in.InfraProviderConfigIdentifier, now)
err = c.createOrFindInfraProviderResource(ctx, resourceSpace, resourceIdentifier, now)
if err != nil {
return nil, err
}
err = c.gitspaceLimiter.Usage(ctx, space.ID, infraProviderResource.InfraProviderType)
if err != nil {
return nil, err
}
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
infraProviderResource, err := c.infraProviderSvc.FindResourceByIdentifier(
ctx,
resourceSpace.ID,
resourceIdentifier)
if err != nil {
return fmt.Errorf("could not find infra provider resource : %q %w", resourceIdentifier, err)
}
codeRepo := types.CodeRepo{
URL: in.CodeRepoURL,
Ref: in.CodeRepoRef,
@ -177,18 +164,11 @@ func (c *Controller) Create(
Created: now,
Updated: now,
SSHTokenIdentifier: in.SSHTokenIdentifier,
AIAgents: in.AIAgents,
CodeRepo: codeRepo,
GitspaceUser: user,
}
gitspaceConfig.InfraProviderResource = *infraProviderResource
if err = c.settingsService.ValidateGitspaceConfigCreate(
ctx, *infraProviderResource, *gitspaceConfig); err != nil {
return err
}
err = c.gitspaceSvc.Create(ctx, gitspaceConfig)
err = c.gitspaceConfigStore.Create(ctx, gitspaceConfig)
if err != nil {
return fmt.Errorf("failed to create gitspace config for : %q %w", identifier, err)
}
@ -203,46 +183,34 @@ func (c *Controller) Create(
func (c *Controller) createOrFindInfraProviderResource(
ctx context.Context,
resourceSpace *types.SpaceCore,
parentSpace *types.Space,
resourceIdentifier string,
infraProviderConfigIdentifier string,
now int64,
) (*types.InfraProviderResource, error) {
var resource *types.InfraProviderResource
var err error
resource, err = c.infraProviderSvc.FindResourceByConfigAndIdentifier(ctx, resourceSpace.ID,
infraProviderConfigIdentifier, resourceIdentifier)
if ((err != nil && errors.Is(err, store.ErrResourceNotFound)) || resource == nil) &&
) error {
_, err := c.infraProviderSvc.FindResourceByIdentifier(
ctx,
parentSpace.ID,
resourceIdentifier)
if err != nil &&
errors.Is(err, store.ErrResourceNotFound) &&
resourceIdentifier == defaultResourceIdentifier {
resource, err = c.autoCreateDefaultResource(ctx, resourceSpace, now)
err = c.autoCreateDefaultResource(ctx, parentSpace, now)
if err != nil {
return nil, err
return err
}
} else if err != nil {
return nil, fmt.Errorf("could not find infra provider resource : %q %w", resourceIdentifier, err)
return fmt.Errorf("could not find infra provider resource : %q %w", resourceIdentifier, err)
}
return resource, err
return err
}
func (c *Controller) autoCreateDefaultResource(
ctx context.Context,
currentSpace *types.SpaceCore,
now int64,
) (*types.InfraProviderResource, error) {
rootSpace, err := c.spaceStore.GetRootSpace(ctx, currentSpace.ID)
if err != nil {
return nil, fmt.Errorf("could not get root space for space %s while autocreating default docker "+
"resource: %w", currentSpace.Path, err)
}
func (c *Controller) autoCreateDefaultResource(ctx context.Context, parentSpace *types.Space, now int64) error {
defaultDockerConfig := &types.InfraProviderConfig{
Identifier: defaultResourceIdentifier,
Name: "default docker infrastructure",
Type: enum.InfraProviderTypeDocker,
SpaceID: rootSpace.ID,
SpacePath: rootSpace.Path,
SpaceID: parentSpace.ID,
SpacePath: parentSpace.Path,
Created: now,
Updated: now,
}
@ -255,25 +223,17 @@ func (c *Controller) autoCreateDefaultResource(
Memory: wrapString("any"),
Disk: wrapString("any"),
Network: wrapString("standard"),
SpaceID: rootSpace.ID,
SpacePath: rootSpace.Path,
SpaceID: parentSpace.ID,
SpacePath: parentSpace.Path,
Created: now,
Updated: now,
}
defaultDockerConfig.Resources = []types.InfraProviderResource{defaultResource}
err = c.infraProviderSvc.CreateConfigAndResources(ctx, defaultDockerConfig)
err := c.infraProviderSvc.CreateInfraProvider(ctx, defaultDockerConfig)
if err != nil {
return nil, fmt.Errorf("could not auto-create the infra provider: %w", err)
return fmt.Errorf("could not auto-create the infra provider: %w", err)
}
resource, err := c.infraProviderSvc.FindResourceByConfigAndIdentifier(ctx, rootSpace.ID,
defaultDockerConfig.Identifier, defaultResourceIdentifier)
if err != nil {
return nil, fmt.Errorf("could not find infra provider resource : %q %w", defaultResourceIdentifier, err)
}
return resource, nil
return nil
}
func wrapString(str string) *string {
@ -281,6 +241,9 @@ func wrapString(str string) *string {
}
func (c *Controller) sanitizeCreateInput(in *CreateInput) error {
if err := check.Identifier(in.Identifier); err != nil {
return err
}
if err := check.Identifier(in.ResourceIdentifier); err != nil {
return err
}
@ -291,34 +254,3 @@ func (c *Controller) sanitizeCreateInput(in *CreateInput) error {
return nil
}
func buildIdentifier(identifier string) (string, error) {
toLower := strings.ToLower(identifier)
err := validateIdentifier(toLower)
if err != nil {
return "", err
}
suffixUID, err := gonanoid.Generate(gitspace.AllowedUIDAlphabet, suffixLen)
if err != nil {
return "", fmt.Errorf("could not generate UID for gitspace config: %q %w", toLower, err)
}
return toLower + "-" + suffixUID, nil
}
func validateIdentifier(identifier string) error {
invalidCharPattern := regexp.MustCompile(`[^a-z0-9-]`)
if invalidCharPattern.MatchString(identifier) {
return usererror.BadRequestf("Identifier %q contains invalid characters: only lowercase letters, "+
"digits, and hyphens are allowed", identifier)
}
if len(identifier) > maxGitspaceConfigIdentifierPrefixLength {
return fmt.Errorf("identifier %q length should be upto 50 characters, is %d characters",
identifier, len(identifier))
}
return nil
}

View File

@ -17,22 +17,94 @@ package gitspace
import (
"context"
"fmt"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
const gitspaceConfigNotFound = "Failed to find gitspace config with identifier "
func (c *Controller) Delete(
ctx context.Context,
session *auth.Session,
spaceRef string,
identifier string,
) error {
err := apiauth.CheckGitspace(ctx, c.authorizer, session, spaceRef, identifier, enum.PermissionGitspaceDelete)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionGitspaceDelete)
if err != nil {
return fmt.Errorf("failed to authorize: %w", err)
}
return c.gitspaceSvc.DeleteGitspaceByIdentifier(ctx, spaceRef, identifier)
gitspaceConfig, err := c.gitspaceConfigStore.FindByIdentifier(ctx, space.ID, identifier)
gitspaceConfig.SpacePath = space.Path
if err != nil || gitspaceConfig == nil {
log.Err(err).Msg(gitspaceConfigNotFound + identifier)
return err
}
instance, _ := c.gitspaceInstanceStore.FindLatestByGitspaceConfigID(ctx, gitspaceConfig.ID)
gitspaceConfig.GitspaceInstance = instance
if instance == nil || instance.State == enum.GitspaceInstanceStateUninitialized {
gitspaceConfig.IsMarkedForDeletion = true
gitspaceConfig.IsDeleted = true
if err = c.gitspaceSvc.UpdateConfig(ctx, gitspaceConfig); err != nil {
return fmt.Errorf("failed to mark gitspace config as deleted: %w", err)
}
return nil
}
// mark can_delete for gitconfig as true so that if delete operation fails, cron job can clean up resources.
gitspaceConfig.IsMarkedForDeletion = true
if err = c.gitspaceSvc.UpdateConfig(ctx, gitspaceConfig); err != nil {
return fmt.Errorf("failed to mark gitspace config is_marked_for_deletion column: %w", err)
}
ctxWithoutCancel := context.WithoutCancel(ctx)
go c.removeGitspace(ctxWithoutCancel, *gitspaceConfig)
return nil
}
func (c *Controller) removeGitspace(ctx context.Context, config types.GitspaceConfig) {
if config.GitspaceInstance.State == enum.GitspaceInstanceStateRunning {
activeTimeEnded := time.Now().UnixMilli()
config.GitspaceInstance.ActiveTimeEnded = &activeTimeEnded
config.GitspaceInstance.TotalTimeUsed =
*(config.GitspaceInstance.ActiveTimeEnded) - *(config.GitspaceInstance.ActiveTimeStarted)
config.GitspaceInstance.State = enum.GitspaceInstanceStateStopping
err := c.gitspaceSvc.UpdateInstance(ctx, config.GitspaceInstance)
if err != nil {
log.Ctx(ctx).Err(err).Msgf("failed to update instance %s before triggering delete",
config.GitspaceInstance.Identifier)
return
}
} else if config.GitspaceInstance.State == enum.GitSpaceInstanceStateCleaning &&
time.Since(time.UnixMilli(config.GitspaceInstance.Updated)).Milliseconds() <=
(gitspaceInstanceCleaningTimedOutMins*60*1000) {
log.Ctx(ctx).Warn().Msgf("gitspace start/stop is already pending for : %q",
config.GitspaceInstance.Identifier)
return
}
if err := c.gitspaceSvc.TriggerDelete(ctx, config); err != nil {
log.Ctx(ctx).Err(err).Msgf("error during triggering delete for gitspace instance %s",
config.GitspaceInstance.Identifier)
config.GitspaceInstance.State = enum.GitspaceInstanceStateError
if updateErr := c.gitspaceSvc.UpdateInstance(ctx, config.GitspaceInstance); updateErr != nil {
log.Ctx(ctx).Err(updateErr).Msgf("failed to update instance %s after error in triggering delete",
config.GitspaceInstance.Identifier)
}
return
}
log.Ctx(ctx).Debug().Msgf("successfully triggered delete for gitspace instance %s",
config.GitspaceInstance.Identifier)
}

View File

@ -28,7 +28,7 @@ import (
var eventMessageMap map[enum.GitspaceEventType]string
func init() {
eventMessageMap = enum.EventsMessageMapping()
eventMessageMap = eventsMessageMapping()
}
func (c *Controller) Events(
@ -39,7 +39,7 @@ func (c *Controller) Events(
page int,
limit int,
) ([]*types.GitspaceEventResponse, int, error) {
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, 0, fmt.Errorf("failed to find space: %w", err)
}
@ -49,20 +49,10 @@ func (c *Controller) Events(
return nil, 0, fmt.Errorf("failed to authorize: %w", err)
}
pagination := types.Pagination{
Page: page,
Size: limit,
}
skipEvents := []enum.GitspaceEventType{
enum.GitspaceEventTypeInfraCleanupStart,
enum.GitspaceEventTypeInfraCleanupCompleted,
enum.GitspaceEventTypeInfraCleanupFailed,
}
filter := &types.GitspaceEventFilter{
Pagination: pagination,
QueryKey: identifier,
SkipEvents: skipEvents,
}
filter := &types.GitspaceEventFilter{}
filter.QueryKey = identifier
filter.Page = page
filter.Size = limit
events, count, err := c.gitspaceEventStore.List(ctx, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list gitspace events for identifier %s: %w", identifier, err)
@ -79,3 +69,55 @@ func (c *Controller) Events(
return result, count, nil
}
func eventsMessageMapping() map[enum.GitspaceEventType]string {
var gitspaceConfigsMap = make(map[enum.GitspaceEventType]string)
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStart] = "Starting gitspace..."
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartCompleted] = "Started gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartFailed] = "Starting gitspace failed"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStop] = "Stopping gitspace..."
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopCompleted] = "Stopped gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopFailed] = "Stopping gitspace failed"
gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerStart] = "Fetching devcontainer config..."
gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerCompleted] = "Fetched devcontainer config"
gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerFailed] = "Fetching devcontainer config failed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningStart] = "Provisioning infrastructure..."
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningCompleted] = "Provisioning infrastructure completed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningFailed] = "Provisioning infrastructure failed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopStart] = "Stopping infrastructure..."
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopCompleted] = "Stopping infrastructure completed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopFailed] = "Stopping infrastructure failed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningStart] = "Deprovisioning infrastructure..."
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningCompleted] = "Deprovisioning infrastructure completed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningFailed] = "Deprovisioning infrastructure failed"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectStart] = "Connecting to the gitspace agent..."
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectCompleted] = "Connected to the gitspace agent"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectFailed] = "Failed connecting to the gitspace agent"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceCreationStart] = "Setting up the gitspace..."
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceCreationCompleted] = "Successfully setup the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceCreationFailed] = "Failed to setup the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStopStart] = "Stopping the gitspace..."
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStopCompleted] = "Successfully stopped the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStopFailed] = "Failed to stop the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceDeletionStart] = "Removing the gitspace..."
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceDeletionCompleted] = "Successfully removed the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceDeletionFailed] = "Failed to remove the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportRunning] = "Gitspace is running"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportStopped] = "Gitspace is stopped"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportUnknown] = "Gitspace is in unknown state"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportError] = "Gitspace has an error"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceAutoStop] = "Triggering auto-stopping due to inactivity..."
return gitspaceConfigsMap
}

View File

@ -30,12 +30,17 @@ func (c *Controller) Find(
spaceRef string,
identifier string,
) (*types.GitspaceConfig, error) {
err := apiauth.CheckGitspace(ctx, c.authorizer, session, spaceRef, identifier, enum.PermissionGitspaceView)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionGitspaceView)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
res, err := c.gitspaceSvc.FindWithLatestInstanceWithSpacePath(ctx, spaceRef, identifier)
res, err := c.gitspaceSvc.Find(ctx, space.Path, identifier)
if err != nil {
return nil, fmt.Errorf("failed to find gitspace: %w", err)
}

View File

@ -21,41 +21,31 @@ import (
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListAllGitspaces all the gitspace with given filter.
// DO NOT USE allSpaceIDs = true for cde-manager. This arg is used only in gitness to list all the gitspaces in gitness
// for all. This is useful to list all the gitspaces in OSS for IDE plugins.
const spaceIsDeleted = "Failed to find space: resource not found"
func (c *Controller) ListAllGitspaces( // nolint:gocognit
ctx context.Context,
session *auth.Session,
filter types.GitspaceFilter,
allSpaceIDs bool,
) ([]*types.GitspaceConfig, error) {
if allSpaceIDs {
leafSpaceIDs, err := c.fetchAllLeafSpaceIDs(ctx)
if err != nil {
return nil, err
}
filter.SpaceIDs = leafSpaceIDs
}
var result []*types.GitspaceConfig
err := c.tx.WithTx(ctx, func(ctx context.Context) (err error) {
allGitspaceConfigs, _, _, err := c.gitspaceSvc.ListGitspacesWithInstance(ctx, filter, false)
allGitspaceConfigs, err := c.gitspaceConfigStore.ListAll(ctx, session.Principal.UID)
if err != nil {
return fmt.Errorf("failed to list gitspace configs: %w", err)
}
var spacesMap = make(map[int64]string)
for idx := range allGitspaceConfigs {
for idx := 0; idx < len(allGitspaceConfigs); idx++ {
if spacesMap[allGitspaceConfigs[idx].SpaceID] == "" {
space, findSpaceErr := c.spaceFinder.FindByRef(ctx, allGitspaceConfigs[idx].SpacePath)
space, findSpaceErr := c.spaceStore.Find(ctx, allGitspaceConfigs[idx].SpaceID)
if findSpaceErr != nil {
if !errors.Is(findSpaceErr, store.ErrResourceNotFound) {
if findSpaceErr.Error() != spaceIsDeleted {
return fmt.Errorf(
"error fetching space %d: %w", allGitspaceConfigs[idx].SpaceID, findSpaceErr)
}
@ -63,6 +53,7 @@ func (c *Controller) ListAllGitspaces( // nolint:gocognit
}
spacesMap[allGitspaceConfigs[idx].SpaceID] = space.Path
}
allGitspaceConfigs[idx].SpacePath = spacesMap[allGitspaceConfigs[idx].SpaceID]
}
authorizedSpaceIDs, err := c.getAuthorizedSpaces(ctx, session, spacesMap)
@ -70,7 +61,10 @@ func (c *Controller) ListAllGitspaces( // nolint:gocognit
return err
}
finalGitspaceConfigs := c.filter(allGitspaceConfigs, authorizedSpaceIDs)
finalGitspaceConfigs, err := c.filterAndPopulateInstanceDetails(ctx, allGitspaceConfigs, authorizedSpaceIDs)
if err != nil {
return err
}
result = finalGitspaceConfigs
@ -81,34 +75,48 @@ func (c *Controller) ListAllGitspaces( // nolint:gocognit
return nil, err
}
for _, gitspaceConfig := range result {
gitspaceConfig.BranchURL = c.gitspaceSvc.GetBranchURL(ctx, gitspaceConfig)
}
return result, nil
}
func (c *Controller) fetchAllLeafSpaceIDs(ctx context.Context) ([]int64, error) {
opts := &types.SpaceFilter{}
rootSpaces, err := c.spaceStore.GetAllRootSpaces(ctx, opts)
if err != nil {
return nil, fmt.Errorf("failed to get root spaces: %w", err)
}
var leafSpaceIDs []int64
for _, rootSpace := range rootSpaces {
spaceIDs, err := c.spaceStore.GetDescendantsIDs(ctx, rootSpace.ID)
if err != nil {
if !errors.Is(err, store.ErrResourceNotFound) {
return nil, fmt.Errorf("failed to get descendants ids: %w", err)
}
}
leafSpaceIDs = append(leafSpaceIDs, spaceIDs...)
}
return leafSpaceIDs, nil
}
func (c *Controller) filter(
func (c *Controller) filterAndPopulateInstanceDetails(
ctx context.Context,
allGitspaceConfigs []*types.GitspaceConfig,
authorizedSpaceIDs map[int64]bool,
) []*types.GitspaceConfig {
return c.getAuthorizedGitspaceConfigs(allGitspaceConfigs, authorizedSpaceIDs)
) ([]*types.GitspaceConfig, error) {
authorizedGitspaceConfigs := c.getAuthorizedGitspaceConfigs(allGitspaceConfigs, authorizedSpaceIDs)
gitspaceInstancesMap, err := c.getLatestInstanceMap(ctx, authorizedGitspaceConfigs)
if err != nil {
return nil, err
}
var result []*types.GitspaceConfig
for _, gitspaceConfig := range authorizedGitspaceConfigs {
instance := gitspaceInstancesMap[gitspaceConfig.ID]
gitspaceConfig.GitspaceInstance = instance
if instance != nil {
gitspaceStateType, stateErr := enum.GetGitspaceStateFromInstance(instance.State, instance.Updated)
if stateErr != nil {
return nil, stateErr
}
gitspaceConfig.State = gitspaceStateType
instance.SpacePath = gitspaceConfig.SpacePath
} else {
gitspaceConfig.State = enum.GitspaceStateUninitialized
}
result = append(result, gitspaceConfig)
}
return result, nil
}
func (c *Controller) getAuthorizedGitspaceConfigs(
@ -116,7 +124,7 @@ func (c *Controller) getAuthorizedGitspaceConfigs(
authorizedSpaceIDs map[int64]bool,
) []*types.GitspaceConfig {
var authorizedGitspaceConfigs = make([]*types.GitspaceConfig, 0)
for idx := range allGitspaceConfigs {
for idx := 0; idx < len(allGitspaceConfigs); idx++ {
if authorizedSpaceIDs[allGitspaceConfigs[idx].SpaceID] {
authorizedGitspaceConfigs = append(authorizedGitspaceConfigs, allGitspaceConfigs[idx])
}
@ -132,11 +140,9 @@ func (c *Controller) getAuthorizedSpaces(
var authorizedSpaceIDs = make(map[int64]bool, 0)
for spaceID, spacePath := range spacesMap {
err := apiauth.CheckGitspace(
ctx, c.authorizer, session, spacePath, "", enum.PermissionGitspaceView,
)
if err != nil && !apiauth.IsNoAccess(err) {
return nil, fmt.Errorf("failed to check gitspace auth for space ID %d: %w", spaceID, err)
authErr := apiauth.CheckGitspace(ctx, c.authorizer, session, spacePath, "", enum.PermissionGitspaceView)
if authErr != nil && !errors.Is(authErr, apiauth.ErrNotAuthorized) {
return nil, fmt.Errorf("failed to check gitspace auth for space ID %d: %w", spaceID, authErr)
}
authorizedSpaceIDs[spaceID] = true
@ -144,3 +150,26 @@ func (c *Controller) getAuthorizedSpaces(
return authorizedSpaceIDs, nil
}
func (c *Controller) getLatestInstanceMap(
ctx context.Context,
authorizedGitspaceConfigs []*types.GitspaceConfig,
) (map[int64]*types.GitspaceInstance, error) {
var authorizedConfigIDs = make([]int64, 0)
for _, config := range authorizedGitspaceConfigs {
authorizedConfigIDs = append(authorizedConfigIDs, config.ID)
}
var gitspaceInstances, err = c.gitspaceInstanceStore.FindAllLatestByGitspaceConfigID(ctx, authorizedConfigIDs)
if err != nil {
return nil, err
}
var gitspaceInstancesMap = make(map[int64]*types.GitspaceInstance)
for _, gitspaceEntry := range gitspaceInstances {
gitspaceInstancesMap[gitspaceEntry.GitSpaceConfigID] = gitspaceEntry
}
return gitspaceInstancesMap, nil
}

View File

@ -32,12 +32,17 @@ func (c *Controller) LogsStream(
spaceRef string,
identifier string,
) (<-chan *sse.Event, <-chan error, error) {
err := apiauth.CheckGitspace(ctx, c.authorizer, session, spaceRef, identifier, enum.PermissionGitspaceView)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, nil, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionGitspaceView)
if err != nil {
return nil, nil, fmt.Errorf("failed to authorize: %w", err)
}
gitspaceConfig, err := c.gitspaceSvc.FindWithLatestInstanceWithSpacePath(ctx, spaceRef, identifier)
gitspaceConfig, err := c.gitspaceConfigStore.FindByIdentifier(ctx, space.ID, identifier)
if err != nil {
return nil, nil, fmt.Errorf("failed to find gitspace config: %w", err)
}

View File

@ -37,7 +37,7 @@ var (
"The URL specified is not valid format.")
ErrRepoMissing = usererror.BadRequest(
"There must be URL or Ref specified fir repo.")
ErrBadURLScheme = usererror.BadRequest("The URL is missing scheme, it must start with http or https")
ErrBadURLScheme = usererror.BadRequest("the URL is missing scheme, it must start with http or https")
)
func (c *Controller) LookupRepo(
@ -48,12 +48,11 @@ func (c *Controller) LookupRepo(
if err := c.sanitizeLookupRepoInput(in); err != nil {
return nil, fmt.Errorf("invalid input: %w", err)
}
space, err := c.spaceFinder.FindByRef(ctx, in.SpaceRef)
space, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckInfraProvider(ctx, c.authorizer, session, space.Path,
"", enum.PermissionInfraProviderView)
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, "", enum.PermissionGitspaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}

View File

@ -21,11 +21,7 @@ import (
"strings"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/controller/gitspace/common"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
gitnessTypes "github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
@ -34,9 +30,7 @@ import (
type UpdateInput struct {
IDE enum.IDEType `json:"ide"`
ResourceIdentifier string `json:"resource_identifier"`
ResourceSpaceRef string `json:"resource_space_ref"`
Name string `json:"name"`
SSHTokenIdentifier string `json:"ssh_token_identifier"`
Identifier string `json:"-"`
SpaceRef string `json:"-"`
}
@ -47,167 +41,27 @@ func (c *Controller) Update(
spaceRef string,
identifier string,
in *UpdateInput,
) (*gitnessTypes.GitspaceConfig, error) {
) error {
in.SpaceRef = spaceRef
in.Identifier = identifier
if err := c.sanitizeUpdateInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
return fmt.Errorf("failed to sanitize input: %w", err)
}
err := apiauth.CheckGitspace(ctx, c.authorizer, session, spaceRef, identifier, enum.PermissionGitspaceEdit)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
return fmt.Errorf("failed to find space: %w", err)
}
gitspaceConfig, err := c.gitspaceSvc.FindWithLatestInstanceWithSpacePath(ctx, spaceRef, identifier)
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionGitspaceEdit)
if err != nil {
return nil, fmt.Errorf("failed to find gitspace config: %w", err)
return fmt.Errorf("failed to authorize: %w", err)
}
// Check the gitspace state. Update can be done only in stopped, error or uninitialized state
currentState := gitspaceConfig.State
if currentState != enum.GitspaceStateStopped &&
currentState != enum.GitspaceStateUninitialized {
return nil, usererror.BadRequest(
"Gitspace update can only be performed when gitspace is stopped or uninitialized",
)
gitspaceConfig, err := c.gitspaceConfigStore.FindByIdentifier(ctx, space.ID, identifier)
if err != nil {
return fmt.Errorf("failed to find gitspace config: %w", err)
}
c.updateIDE(in, gitspaceConfig)
if err := c.handleSSHToken(in, gitspaceConfig); err != nil {
return nil, err
}
if err := c.updateResourceIdentifier(ctx, in, gitspaceConfig); err != nil {
return nil, err
}
// TODO Update with proper locks
err = c.gitspaceSvc.UpdateConfig(ctx, gitspaceConfig)
if err != nil {
return nil, fmt.Errorf("failed to update gitspace config: %w", err)
}
return gitspaceConfig, nil
}
func (c *Controller) updateIDE(in *UpdateInput, gitspaceConfig *gitnessTypes.GitspaceConfig) {
if in.IDE != "" && in.IDE != gitspaceConfig.IDE {
gitspaceConfig.IDE = in.IDE
gitspaceConfig.IsMarkedForReset = true
}
// Always clear SSH token if IDE is VS Code Web
if gitspaceConfig.IDE == enum.IDETypeVSCodeWeb {
gitspaceConfig.SSHTokenIdentifier = ""
}
}
func (c *Controller) handleSSHToken(in *UpdateInput, gitspaceConfig *gitnessTypes.GitspaceConfig) error {
if in.SSHTokenIdentifier != "" {
if gitspaceConfig.IDE == enum.IDETypeVSCodeWeb {
return usererror.BadRequest("SSH token should not be sent with VS Code Web IDE")
}
// For other IDEs, update the token
if in.SSHTokenIdentifier != gitspaceConfig.SSHTokenIdentifier {
gitspaceConfig.SSHTokenIdentifier = in.SSHTokenIdentifier
gitspaceConfig.IsMarkedForReset = true
}
}
return nil
}
func (c *Controller) updateResourceIdentifier(
ctx context.Context,
in *UpdateInput,
gitspaceConfig *gitnessTypes.GitspaceConfig,
) error {
// Handle resource identifier update similar to create, but only if provided
if in.ResourceIdentifier == "" || in.ResourceIdentifier == gitspaceConfig.InfraProviderResource.UID {
return nil
}
if gitspaceConfig.InfraProviderResource.UID == "default" {
return usererror.BadRequest("The default resource cannot be updated in harness open source")
}
// Set resource space reference if not provided
if in.ResourceSpaceRef == "" {
rootSpaceRef, _, err := paths.DisectRoot(in.SpaceRef)
if err != nil {
return fmt.Errorf("unable to find root space path for %s: %w", in.SpaceRef, err)
}
in.ResourceSpaceRef = rootSpaceRef
}
// Find spaces and resources
existingResource, newResource, err := c.getResources(ctx, in, gitspaceConfig)
if err != nil {
return err
}
// Validate the resource spec change
markForInfraReset, err := common.IsResourceSpecChangeAllowed(existingResource, newResource)
if err != nil {
return err
}
gitspaceConfig.IsMarkedForInfraReset = gitspaceConfig.IsMarkedForInfraReset || markForInfraReset
gitspaceConfig.InfraProviderResource = *newResource
return nil
}
func (c *Controller) getResources(
ctx context.Context,
in *UpdateInput,
gitspaceConfig *gitnessTypes.GitspaceConfig,
) (*gitnessTypes.InfraProviderResource, *gitnessTypes.InfraProviderResource, error) {
// Get existing resource space and resource
existingSpace, err := c.spaceFinder.FindByRef(
ctx,
gitspaceConfig.InfraProviderResource.SpacePath,
)
if err != nil {
return nil, nil, fmt.Errorf("failed to find resource space: %w", err)
}
existingResource, err := c.infraProviderSvc.FindResourceByConfigAndIdentifier(
ctx,
existingSpace.ID,
gitspaceConfig.InfraProviderResource.InfraProviderConfigIdentifier,
gitspaceConfig.InfraProviderResource.UID,
)
if err != nil {
return nil, nil, fmt.Errorf(
"could not find existing infra provider resource: %w",
err,
)
}
// Get new resource space and resource
newSpace, err := c.spaceFinder.FindByRef(
ctx,
in.ResourceSpaceRef,
)
if err != nil {
return nil, nil, fmt.Errorf("failed to find resource space: %w", err)
}
newResource, err := c.infraProviderSvc.FindResourceByConfigAndIdentifier(
ctx,
newSpace.ID,
gitspaceConfig.InfraProviderResource.InfraProviderConfigIdentifier,
in.ResourceIdentifier,
)
if err != nil {
return nil, nil, fmt.Errorf(
"could not find infra provider resource %q: %w",
in.ResourceIdentifier,
err,
)
}
return existingResource, newResource, nil
return c.gitspaceSvc.UpdateConfig(ctx, gitspaceConfig)
}
func (c *Controller) sanitizeUpdateInput(in *UpdateInput) error {

View File

@ -20,9 +20,7 @@ import (
"github.com/harness/gitness/app/gitspace/logutil"
"github.com/harness/gitness/app/gitspace/scm"
"github.com/harness/gitness/app/services/gitspace"
"github.com/harness/gitness/app/services/gitspacesettings"
"github.com/harness/gitness/app/services/infraprovider"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
@ -38,28 +36,28 @@ func ProvideController(
tx dbtx.Transactor,
authorizer authz.Authorizer,
infraProviderSvc *infraprovider.Service,
configStore store.GitspaceConfigStore,
instanceStore store.GitspaceInstanceStore,
spaceStore store.SpaceStore,
spaceFinder refcache.SpaceFinder,
eventStore store.GitspaceEventStore,
statefulLogger *logutil.StatefulLogger,
scm *scm.SCM,
repoStore store.RepoStore,
gitspaceSvc *gitspace.Service,
gitspaceLimiter limiter.Gitspace,
repoFinder refcache.RepoFinder,
settingsService gitspacesettings.Service,
) *Controller {
return NewController(
tx,
authorizer,
infraProviderSvc,
configStore,
instanceStore,
spaceStore,
spaceFinder,
eventStore,
statefulLogger,
scm,
repoStore,
gitspaceSvc,
gitspaceLimiter,
repoFinder,
settingsService,
)
}

View File

@ -17,59 +17,23 @@ package infraprovider
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/infraprovider"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/types/enum"
"github.com/harness/gitness/app/store"
)
const NoResourceIdentifier = ""
type ConfigInput struct {
Identifier string `json:"identifier" yaml:"identifier"`
SpaceRef string `json:"space_ref" yaml:"space_ref"`
Name string `json:"name" yaml:"name"`
Type enum.InfraProviderType `json:"type" yaml:"type"`
Metadata map[string]any `json:"metadata" yaml:"metadata"`
}
type ResourceInput struct {
Identifier string `json:"identifier" yaml:"identifier"`
Name string `json:"name" yaml:"name"`
InfraProviderType enum.InfraProviderType `json:"infra_provider_type" yaml:"infra_provider_type"`
CPU *string `json:"cpu" yaml:"cpu"`
Memory *string `json:"memory" yaml:"memory"`
Disk *string `json:"disk" yaml:"disk"`
Network *string `json:"network" yaml:"network"`
Region string `json:"region" yaml:"region"`
Metadata map[string]string `json:"metadata" yaml:"metadata"`
GatewayHost *string `json:"gateway_host" yaml:"gateway_host"`
GatewayPort *string `json:"gateway_port" yaml:"gateway_port"`
}
type AutoCreateInput struct {
Config ConfigInput `json:"config" yaml:"config"`
Resources []ResourceInput `json:"resources" yaml:"resources"`
}
type TemplateInput struct {
Identifier string `json:"identifier" yaml:"identifier"`
Description string `json:"description" yaml:"description"`
Data string `json:"data" yaml:"data"`
}
type Controller struct {
authorizer authz.Authorizer
spaceFinder refcache.SpaceFinder
spaceStore store.SpaceStore
infraproviderSvc *infraprovider.Service
}
func NewController(
authorizer authz.Authorizer,
spaceFinder refcache.SpaceFinder,
spaceStore store.SpaceStore,
infraproviderSvc *infraprovider.Service,
) *Controller {
return &Controller{
authorizer: authorizer,
spaceFinder: spaceFinder,
spaceStore: spaceStore,
infraproviderSvc: infraproviderSvc,
}
}

View File

@ -0,0 +1,113 @@
// 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 infraprovider
import (
"context"
"fmt"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
const NoResourceIdentifier = ""
type CreateInput struct {
Identifier string `json:"identifier" yaml:"identifier"`
SpaceRef string `json:"space_ref" yaml:"space_ref"` // Ref of the parent space
Name string `json:"name" yaml:"name"`
Type enum.InfraProviderType `json:"type" yaml:"type"`
Metadata map[string]string `json:"metadata" yaml:"metadata"`
Resources []ResourceInput `json:"resources" yaml:"resources"`
}
type ResourceInput struct {
Identifier string `json:"identifier" yaml:"identifier"`
Name string `json:"name" yaml:"name"`
InfraProviderType enum.InfraProviderType `json:"infra_provider_type" yaml:"infra_provider_type"`
CPU *string `json:"cpu" yaml:"cpu"`
Memory *string `json:"memory" yaml:"memory"`
Disk *string `json:"disk" yaml:"disk"`
Network *string `json:"network" yaml:"network"`
Region []string `json:"region" yaml:"region"`
Metadata map[string]string `json:"metadata" yaml:"metadata"`
GatewayHost *string `json:"gateway_host" yaml:"gateway_host"`
GatewayPort *string `json:"gateway_port" yaml:"gateway_port"`
}
type TemplateInput struct {
Identifier string `json:"identifier" yaml:"identifier"`
Description string `json:"description" yaml:"description"`
Data string `json:"data" yaml:"data"`
}
// Create creates a new infra provider.
func (c *Controller) Create(
ctx context.Context,
session auth.Session,
in CreateInput,
) (*types.InfraProviderConfig, error) {
if err := c.sanitizeCreateInput(in); err != nil {
return nil, fmt.Errorf("invalid input: %w", err)
}
parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref %q : %w", in.SpaceRef, err)
}
if err = apiauth.CheckInfraProvider(
ctx,
c.authorizer,
&session,
parentSpace.Path,
NoResourceIdentifier,
enum.PermissionInfraProviderEdit); err != nil {
return nil, err
}
now := time.Now().UnixMilli()
infraProviderConfig := c.MapToInfraProviderConfig(in, parentSpace, now)
err = c.infraproviderSvc.CreateInfraProvider(ctx, infraProviderConfig)
if err != nil {
return nil, fmt.Errorf("unable to create the infraprovider: %q %w", infraProviderConfig.Identifier, err)
}
return infraProviderConfig, nil
}
func (c *Controller) MapToInfraProviderConfig(
in CreateInput,
parentSpace *types.Space,
now int64,
) *types.InfraProviderConfig {
infraProviderConfig := &types.InfraProviderConfig{
Identifier: in.Identifier,
Name: in.Name,
SpaceID: parentSpace.ID,
Type: in.Type,
Created: now,
Updated: now,
}
infraProviderConfig.Resources = mapToResourceEntity(in.Resources, *parentSpace, now)
return infraProviderConfig
}
func (c *Controller) sanitizeCreateInput(in CreateInput) error {
if err := check.Identifier(in.Identifier); err != nil {
return err
}
return nil
}

View File

@ -1,83 +0,0 @@
// 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 infraprovider
import (
"context"
"fmt"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
// CreateConfig creates a new infra provider config.
func (c *Controller) CreateConfig(
ctx context.Context,
session auth.Session,
in ConfigInput,
) (*types.InfraProviderConfig, error) {
if err := c.sanitizeCreateInput(in); err != nil {
return nil, fmt.Errorf("invalid input: %w", err)
}
parentSpace, err := c.spaceFinder.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref %q : %w", in.SpaceRef, err)
}
if err = apiauth.CheckInfraProvider(
ctx,
c.authorizer,
&session,
parentSpace.Path,
NoResourceIdentifier,
enum.PermissionInfraProviderEdit,
); err != nil {
return nil, err
}
now := time.Now().UnixMilli()
infraProviderConfig := c.MapToInfraProviderConfig(in, parentSpace, now)
err = c.infraproviderSvc.CreateConfig(ctx, infraProviderConfig)
if err != nil {
return nil, fmt.Errorf("unable to create the infraprovider: %q %w", infraProviderConfig.Identifier, err)
}
return infraProviderConfig, nil
}
func (c *Controller) MapToInfraProviderConfig(
in ConfigInput,
space *types.SpaceCore,
now int64,
) *types.InfraProviderConfig {
return &types.InfraProviderConfig{
Identifier: in.Identifier,
Name: in.Name,
SpaceID: space.ID,
SpacePath: space.Path,
Type: in.Type,
Created: now,
Updated: now,
Metadata: in.Metadata,
}
}
func (c *Controller) sanitizeCreateInput(in ConfigInput) error {
if err := check.Identifier(in.Identifier); err != nil {
return err
}
return nil
}

View File

@ -17,6 +17,7 @@ package infraprovider
import (
"context"
"fmt"
"strings"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
@ -34,7 +35,7 @@ func (c *Controller) CreateTemplate(
spaceRef string,
) (*types.InfraProviderTemplate, error) {
now := time.Now().UnixMilli()
parentSpace, err := c.spaceFinder.FindByRef(ctx, spaceRef)
parentSpace, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
@ -44,8 +45,7 @@ func (c *Controller) CreateTemplate(
session,
parentSpace.Path,
NoResourceIdentifier,
enum.PermissionInfraProviderEdit,
); err != nil {
enum.PermissionInfraProviderEdit); err != nil {
return nil, err
}
@ -83,7 +83,7 @@ func (c *Controller) CreateResources(
return nil, fmt.Errorf("invalid input: %w", err)
}
now := time.Now().UnixMilli()
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
parentSpace, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
@ -91,42 +91,40 @@ func (c *Controller) CreateResources(
ctx,
c.authorizer,
&session,
space.Path,
parentSpace.Path,
NoResourceIdentifier,
enum.PermissionInfraProviderEdit,
); err != nil {
enum.PermissionInfraProviderEdit); err != nil {
return nil, err
}
resources := c.MapToResourceEntity(in, space, now)
err = c.infraproviderSvc.CreateResources(ctx, space.ID, resources, configIdentifier)
infraProviderConfig, err := c.infraproviderSvc.Find(ctx, parentSpace, configIdentifier)
if err != nil {
return nil, fmt.Errorf("failed to find infraprovider config by ref: %q %w", infraProviderConfig.Identifier, err)
}
resources := mapToResourceEntity(in, *parentSpace, now)
err = c.infraproviderSvc.CreateResources(ctx, resources, infraProviderConfig.ID)
if err != nil {
return nil, err
}
return resources, nil
}
func (c *Controller) MapToResourceEntity(
in []ResourceInput,
space *types.SpaceCore,
now int64,
) []types.InfraProviderResource {
func mapToResourceEntity(in []ResourceInput, parentSpace types.Space, now int64) []types.InfraProviderResource {
var resources []types.InfraProviderResource
for _, res := range in {
infraProviderResource := types.InfraProviderResource{
UID: res.Identifier,
InfraProviderType: res.InfraProviderType,
Name: res.Name,
SpaceID: space.ID,
SpaceID: parentSpace.ID,
CPU: res.CPU,
Memory: res.Memory,
Disk: res.Disk,
Network: res.Network,
Region: res.Region,
Region: strings.Join(res.Region, " "), // TODO fix
Metadata: res.Metadata,
Created: now,
Updated: now,
SpacePath: space.Path,
SpacePath: parentSpace.Path,
}
resources = append(resources, infraProviderResource)
}

View File

@ -1,42 +0,0 @@
// 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 infraprovider
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) DeleteConfig(
ctx context.Context,
session *auth.Session,
spaceRef string,
identifier string,
) error {
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
if err != nil {
return fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckInfraProvider(ctx, c.authorizer, session, space.Path,
identifier, enum.PermissionInfraProviderDelete)
if err != nil {
return fmt.Errorf("failed to authorize: %w", err)
}
return c.infraproviderSvc.DeleteConfig(ctx, space, identifier, true)
}

View File

@ -1,50 +0,0 @@
// 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 infraprovider
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) DeleteResource(
ctx context.Context,
session *auth.Session,
spaceID int64,
infraProviderConfigIdentifier string,
infraProviderResourceIdentifier string,
) error {
space, err := c.spaceFinder.FindByID(ctx, spaceID)
if err != nil {
return fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckInfraProvider(
ctx,
c.authorizer,
session,
space.Path,
"",
enum.PermissionInfraProviderDelete,
)
if err != nil {
return fmt.Errorf("failed to authorize: %w", err)
}
return c.infraproviderSvc.DeleteResource(ctx, spaceID, infraProviderConfigIdentifier,
infraProviderResourceIdentifier, true)
}

View File

@ -18,24 +18,25 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) Find(
ctx context.Context,
_ *auth.Session,
session *auth.Session,
spaceRef string,
identifier string,
) (*types.InfraProviderConfig, error) {
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
// todo: add acl check with PermissionInfraProviderView once infra provider resource is added to access control
// err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionGitspaceView)
// if err != nil {
// return nil, fmt.Errorf("failed to authorize: %w", err)
// }
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionInfraProviderView)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
return c.infraproviderSvc.Find(ctx, space, identifier)
}

View File

@ -1,46 +0,0 @@
// 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 infraprovider
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) List(
ctx context.Context,
session *auth.Session,
spaceRef string,
applyACLFilter bool,
) ([]*types.InfraProviderConfig, error) {
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckInfraProvider(ctx, c.authorizer, session, space.Path, "", enum.PermissionInfraProviderView)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
filter := types.InfraProviderConfigFilter{
SpaceIDs: []int64{space.ID},
ApplyResourcesACL: applyACLFilter,
}
return c.infraproviderSvc.List(ctx, &filter)
}

View File

@ -1,59 +0,0 @@
// 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 infraprovider
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// ListResources retrieves all resources for an infrastructure provider.
func (c *Controller) ListResources(
ctx context.Context,
session *auth.Session,
spaceRef, infraProviderIdentifier string,
) ([]*types.InfraProviderResource, error) {
// Find the space for authorization checks
space, err := c.spaceFinder.FindByRef(ctx, spaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
// Check authorization
err = apiauth.CheckInfraProvider(ctx, c.authorizer, session, space.Path, "", enum.PermissionInfraProviderView)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
// Find the infra provider config using the correct method
config, err := c.infraproviderSvc.Find(ctx, space, infraProviderIdentifier)
if err != nil {
return nil, fmt.Errorf("failed to find infra provider: %w", err)
}
// The config from Find() already has its resources populated, so we can just use them
// Create pointers for the resources from the populated config
resources := make([]*types.InfraProviderResource, len(config.Resources))
for i := range config.Resources {
resources[i] = &config.Resources[i]
}
return resources, nil
}

View File

@ -17,7 +17,7 @@ package infraprovider
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/infraprovider"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/google/wire"
)
@ -29,8 +29,8 @@ var WireSet = wire.NewSet(
func ProvideController(
authorizer authz.Authorizer,
spaceFinder refcache.SpaceFinder,
spaceStore store.SpaceStore,
infraproviderSvc *infraprovider.Service,
) *Controller {
return NewController(authorizer, spaceFinder, infraproviderSvc)
return NewController(authorizer, spaceStore, infraproviderSvc)
}

View File

@ -17,7 +17,6 @@ package keywordsearch
import (
"context"
"fmt"
"maps"
"math"
"github.com/harness/gitness/app/api/usererror"
@ -34,7 +33,7 @@ func (c *Controller) Search(
in types.SearchInput,
) (types.SearchResult, error) {
if in.Query == "" {
return types.SearchResult{}, usererror.BadRequest("Query cannot be empty.")
return types.SearchResult{}, usererror.BadRequest("query cannot be empty.")
}
if len(in.RepoPaths) == 0 && len(in.SpacePaths) == 0 {
@ -52,10 +51,12 @@ func (c *Controller) Search(
return types.SearchResult{}, fmt.Errorf("failed to search repos by space path: %w", err)
}
maps.Copy(repoIDToPathMap, spaceRepoIDToPathMap)
for repoID, repoPath := range spaceRepoIDToPathMap {
repoIDToPathMap[repoID] = repoPath
}
if len(repoIDToPathMap) == 0 {
return types.SearchResult{}, usererror.NotFound("No repositories found")
return types.SearchResult{}, usererror.NotFound("no repositories found")
}
repoIDs := make([]int64, 0, len(repoIDToPathMap))
@ -118,7 +119,9 @@ func (c *Controller) getReposBySpacePaths(
return nil, fmt.Errorf("failed to search repos by space path: %w", err)
}
maps.Copy(repoIDToPathMap, m)
for repoID, repoPath := range m {
repoIDToPathMap[repoID] = repoPath
}
}
return repoIDToPathMap, nil
}

View File

@ -1,65 +0,0 @@
// 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 lfs
import (
"context"
"fmt"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authn"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/token"
)
func (c *Controller) Authenticate(
ctx context.Context,
session *auth.Session,
repoRef string,
) (*AuthenticateResponse, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repository: %w", err)
}
gitLFSEnabled, err := settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeyGitLFSEnabled,
settings.DefaultGitLFSEnabled,
)
if err != nil {
return nil, fmt.Errorf("failed to check settings for Git LFS enabled: %w", err)
}
if !gitLFSEnabled {
return nil, usererror.ErrGitLFSDisabled
}
jwt, err := c.remoteAuth.GenerateToken(ctx, session.Principal.ID, session.Principal.Type, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to generate auth token: %w", err)
}
return &AuthenticateResponse{
Header: map[string]string{
"Authorization": authn.HeaderTokenPrefixRemoteAuth + jwt,
},
HRef: c.urlProvider.GenerateGITCloneURL(ctx, repoRef) + "/info/lfs",
ExpiresIn: token.RemoteAuthTokenLifeTime,
}, nil
}

View File

@ -1,119 +0,0 @@
// 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 lfs
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/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/services/remoteauth"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/blob"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
const (
lfsObjectsPathFormat = "lfs/%s"
)
type Controller struct {
authorizer authz.Authorizer
repoFinder refcache.RepoFinder
repoStore store.RepoStore
principalStore store.PrincipalStore
lfsStore store.LFSObjectStore
blobStore blob.Store
remoteAuth remoteauth.Service
urlProvider url.Provider
settings *settings.Service
}
func NewController(
authorizer authz.Authorizer,
repoFinder refcache.RepoFinder,
repoStore store.RepoStore,
principalStore store.PrincipalStore,
lfsStore store.LFSObjectStore,
blobStore blob.Store,
remoteAuth remoteauth.Service,
urlProvider url.Provider,
settings *settings.Service,
) *Controller {
return &Controller{
authorizer: authorizer,
repoFinder: repoFinder,
repoStore: repoStore,
principalStore: principalStore,
lfsStore: lfsStore,
blobStore: blobStore,
remoteAuth: remoteAuth,
urlProvider: urlProvider,
settings: settings,
}
}
func (c *Controller) getRepoCheckAccessAndSetting(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.RepositoryCore, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
}
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
gitLFSEnabled, err := settings.RepoGet(
ctx,
c.settings,
repo.ID,
settings.KeyGitLFSEnabled,
settings.DefaultGitLFSEnabled,
)
if err != nil {
return nil, fmt.Errorf("failed to check settings for Git LFS enabled: %w", err)
}
if !gitLFSEnabled {
return nil, usererror.ErrGitLFSDisabled
}
return repo, nil
}
func getLFSObjectPath(oid string) string {
return fmt.Sprintf(lfsObjectsPathFormat, oid)
}

View File

@ -1,72 +0,0 @@
// 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 lfs
import (
"context"
"fmt"
"io"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
type Content struct {
Data io.ReadCloser
Size int64
}
func (c *Content) Read(p []byte) (n int, err error) {
return c.Data.Read(p)
}
func (c *Content) Close() error {
return c.Data.Close()
}
func (c *Controller) Download(ctx context.Context,
session *auth.Session,
repoRef string,
oid string,
) (*Content, error) {
repo, err := c.getRepoCheckAccessAndSetting(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
return c.DownloadNoAuth(ctx, repo.ID, oid)
}
func (c *Controller) DownloadNoAuth(
ctx context.Context,
repoID int64,
oid string,
) (*Content, error) {
obj, err := c.lfsStore.Find(ctx, repoID, oid)
if err != nil {
return nil, fmt.Errorf("failed to find the oid %q for the repo: %w", oid, err)
}
objPath := getLFSObjectPath(oid)
file, err := c.blobStore.Download(ctx, objPath)
if err != nil {
return nil, fmt.Errorf("failed to download file from blobstore: %w", err)
}
return &Content{
Data: file,
Size: obj.Size,
}, nil
}

View File

@ -1,23 +0,0 @@
// 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 lfs
var (
// These are per-object errors when returned status code is 200.
errNotFound = ObjectError{
Code: 404,
Message: "The object does not exist on the server.",
}
)

View File

@ -1,138 +0,0 @@
// 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 lfs
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) LFSTransfer(ctx context.Context,
session *auth.Session,
repoRef string,
in *TransferInput,
) (*TransferOutput, error) {
reqPermission := enum.PermissionRepoView
if in.Operation == enum.GitLFSOperationTypeUpload {
reqPermission = enum.PermissionRepoPush
}
var additionalAllowedRepoStates = []enum.RepoState{enum.RepoStateMigrateGitPush}
repo, err := c.getRepoCheckAccessAndSetting(ctx, session, repoRef,
reqPermission, additionalAllowedRepoStates...)
if err != nil {
return nil, err
}
// TODO check if server supports client's transfer adapters
var objResponses []ObjectResponse
switch in.Operation {
case enum.GitLFSOperationTypeDownload:
for _, obj := range in.Objects {
var objResponse = ObjectResponse{
Pointer: Pointer{
OId: obj.OId,
Size: obj.Size,
},
}
object, err := c.lfsStore.Find(ctx, repo.ID, obj.OId)
if errors.Is(err, store.ErrResourceNotFound) {
objResponse.Error = &errNotFound
objResponses = append(objResponses, objResponse)
continue
}
if err != nil {
return nil, fmt.Errorf("failed to find object: %w", err)
}
// size is not a required query param for download hence nil
downloadURL := getRedirectRef(ctx, c.urlProvider, repoRef, obj.OId, nil)
objResponse = ObjectResponse{
Pointer: Pointer{
OId: object.OID,
Size: object.Size,
},
Actions: map[string]Action{
"download": {
Href: downloadURL,
Header: map[string]string{"Content-Type": "application/octet-stream"},
},
},
}
objResponses = append(objResponses, objResponse)
}
case enum.GitLFSOperationTypeUpload:
for _, obj := range in.Objects {
objResponse := ObjectResponse{
Pointer: Pointer{
OId: obj.OId,
Size: obj.Size,
},
}
// we dont create the object in lfs store here as the upload might fail in blob store.
_, err := c.lfsStore.Find(ctx, repo.ID, obj.OId)
if err == nil {
// no need to re-upload existing LFS objects
objResponses = append(objResponses, objResponse)
continue
}
if !errors.Is(err, store.ErrResourceNotFound) {
return nil, fmt.Errorf("failed to find object: %w", err)
}
uploadURL := getRedirectRef(ctx, c.urlProvider, repoRef, obj.OId, &obj.Size)
objResponse.Actions = map[string]Action{
"upload": {
Href: uploadURL,
Header: map[string]string{"Content-Type": "application/octet-stream"},
},
}
objResponses = append(objResponses, objResponse)
}
default:
return nil, usererror.BadRequestf("Git LFS operation %q is not supported", in.Operation)
}
return &TransferOutput{
Transfer: enum.GitLFSTransferTypeBasic,
Objects: objResponses,
}, nil
}
func getRedirectRef(ctx context.Context, urlProvider url.Provider, repoPath, oID string, size *int64) string {
baseGitURL := urlProvider.GenerateGITCloneURL(ctx, repoPath)
queryParams := "oid=" + oID
if size != nil {
queryParams += "&size=" + strconv.FormatInt(*size, 10)
}
return baseGitURL + "/info/lfs/objects/?" + queryParams
}

View File

@ -1,71 +0,0 @@
// 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 lfs
import (
"time"
"github.com/harness/gitness/types/enum"
)
type Reference struct {
Name string `json:"name"`
}
// Pointer contains LFS pointer data.
type Pointer struct {
OId string `json:"oid"`
Size int64 `json:"size"`
}
type TransferInput struct {
Operation enum.GitLFSOperationType `json:"operation"`
Transfers []enum.GitLFSTransferType `json:"transfers,omitempty"`
Ref *Reference `json:"ref,omitempty"`
Objects []Pointer `json:"objects"`
HashAlgo string `json:"hash_algo,omitempty"`
}
// ObjectError defines the JSON structure returned to the client in case of an error.
type ObjectError struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Action provides a structure with information about next actions fo the object.
type Action struct {
Href string `json:"href"`
Header map[string]string `json:"header,omitempty"`
ExpiresIn *time.Duration `json:"expires_in,omitempty"`
}
// ObjectResponse is object metadata as seen by clients of the LFS server.
type ObjectResponse struct {
Pointer
Authenticated *bool `json:"authenticated,omitempty"`
Actions map[string]Action `json:"actions"`
Error *ObjectError `json:"error,omitempty"`
}
type TransferOutput struct {
Transfer enum.GitLFSTransferType `json:"transfer"`
Objects []ObjectResponse `json:"objects"`
}
type AuthenticateResponse struct {
Header map[string]string `json:"header"`
HRef string `json:"href"`
ExpiresIn time.Duration `json:"expires_in"`
}

View File

@ -1,106 +0,0 @@
// 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 lfs
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type UploadOut struct {
ObjectPath string `json:"object_path"`
}
func (c *Controller) Upload(ctx context.Context,
session *auth.Session,
repoRef string,
pointer Pointer,
file io.Reader,
) (*UploadOut, error) {
var additionalAllowedRepoStates = []enum.RepoState{enum.RepoStateMigrateGitPush}
repoCore, err := c.getRepoCheckAccessAndSetting(ctx, session, repoRef,
enum.PermissionRepoPush, additionalAllowedRepoStates...)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
if file == nil {
return nil, usererror.BadRequest("No file or content provided")
}
_, err = c.lfsStore.Find(ctx, repoCore.ID, pointer.OId)
if err != nil && !errors.Is(err, store.ErrResourceNotFound) {
return nil, fmt.Errorf("failed to check if object exists: %w", err)
}
if err == nil {
return nil, usererror.Conflict("LFS object already exists and cannot be modified")
}
limitedReader := io.LimitReader(file, pointer.Size)
content, err := io.ReadAll(limitedReader)
if err != nil {
return nil, fmt.Errorf("failed to read uploaded content: %w", err)
}
hasher := sha256.New()
hasher.Write(content)
calculatedHash := hex.EncodeToString(hasher.Sum(nil))
expectedHash := strings.TrimPrefix(pointer.OId, "sha256:")
if calculatedHash != expectedHash {
return nil, usererror.BadRequest("content hash doesn't match provided OID")
}
contentReader := bytes.NewReader(content)
objPath := getLFSObjectPath(pointer.OId)
err = c.blobStore.Upload(ctx, contentReader, objPath)
if err != nil {
return nil, fmt.Errorf("failed to upload file: %w", err)
}
now := time.Now()
object := &types.LFSObject{
OID: pointer.OId,
Size: pointer.Size,
Created: now.UnixMilli(),
CreatedBy: session.Principal.ID,
RepoID: repoCore.ID,
}
// create the object in lfs store after successful upload to the blob store.
err = c.lfsStore.Create(ctx, object)
if err != nil && !errors.Is(err, store.ErrDuplicate) {
return nil, fmt.Errorf("failed to create object: %w", err)
}
return &UploadOut{
ObjectPath: objPath,
}, nil
}

View File

@ -16,14 +16,12 @@ package limiter
import (
"context"
"github.com/harness/gitness/types/enum"
)
// Gitspace is an interface for managing gitspace limitations.
type Gitspace interface {
// Usage checks if the total usage for the root space and all sub-spaces is under a limit.
Usage(ctx context.Context, spaceID int64, infraProviderType enum.InfraProviderType) error
Usage(ctx context.Context, spaceID int64) error
}
var _ Gitspace = (*UnlimitedUsage)(nil)
@ -36,6 +34,6 @@ func NewUnlimitedUsage() Gitspace {
return UnlimitedUsage{}
}
func (UnlimitedUsage) Usage(_ context.Context, _ int64, _ enum.InfraProviderType) error {
func (UnlimitedUsage) Usage(_ context.Context, _ int64) error {
return nil
}

View File

@ -16,7 +16,6 @@ package logs
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/livelog"
)
@ -24,32 +23,32 @@ import (
type Controller struct {
authorizer authz.Authorizer
executionStore store.ExecutionStore
repoStore store.RepoStore
pipelineStore store.PipelineStore
stageStore store.StageStore
stepStore store.StepStore
logStore store.LogStore
logStream livelog.LogStream
repoFinder refcache.RepoFinder
}
func NewController(
authorizer authz.Authorizer,
executionStore store.ExecutionStore,
repoStore store.RepoStore,
pipelineStore store.PipelineStore,
stageStore store.StageStore,
stepStore store.StepStore,
logStore store.LogStore,
logStream livelog.LogStream,
repoFinder refcache.RepoFinder,
) *Controller {
return &Controller{
authorizer: authorizer,
executionStore: executionStore,
repoStore: repoStore,
pipelineStore: pipelineStore,
stageStore: stageStore,
stepStore: stepStore,
logStore: logStore,
logStream: logStream,
repoFinder: repoFinder,
}
}

View File

@ -35,11 +35,10 @@ func (c *Controller) Find(
stageNum int,
stepNum int,
) ([]*livelog.Line, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)

View File

@ -33,16 +33,14 @@ func (c *Controller) Tail(
stageNum int,
stepNum int,
) (<-chan *livelog.Line, <-chan error, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, nil, fmt.Errorf("failed to authorize pipeline: %w", err)
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)
if err != nil {
return nil, nil, fmt.Errorf("failed to find pipeline: %w", err)

View File

@ -16,7 +16,6 @@ package logs
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/livelog"
@ -31,13 +30,13 @@ var WireSet = wire.NewSet(
func ProvideController(
authorizer authz.Authorizer,
executionStore store.ExecutionStore,
repoStore store.RepoStore,
pipelineStore store.PipelineStore,
stageStore store.StageStore,
stepStore store.StepStore,
logStore store.LogStore,
logStream livelog.LogStream,
repoFinder refcache.RepoFinder,
) *Controller {
return NewController(authorizer, executionStore,
pipelineStore, stageStore, stepStore, logStore, logStream, repoFinder)
return NewController(authorizer, executionStore, repoStore,
pipelineStore, stageStore, stepStore, logStore, logStream)
}

View File

@ -23,10 +23,8 @@ import (
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
repoevents "github.com/harness/gitness/app/events/repo"
"github.com/harness/gitness/app/services/migrate"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
@ -52,9 +50,6 @@ type Controller struct {
tx dbtx.Transactor
spaceStore store.SpaceStore
repoStore store.RepoStore
spaceFinder refcache.SpaceFinder
repoFinder refcache.RepoFinder
eventReporter *repoevents.Reporter
}
func NewController(
@ -72,9 +67,6 @@ func NewController(
tx dbtx.Transactor,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
spaceFinder refcache.SpaceFinder,
repoFinder refcache.RepoFinder,
eventReporter *repoevents.Reporter,
) *Controller {
return &Controller{
authorizer: authorizer,
@ -91,29 +83,20 @@ func NewController(
tx: tx,
spaceStore: spaceStore,
repoStore: repoStore,
spaceFinder: spaceFinder,
repoFinder: repoFinder,
eventReporter: eventReporter,
}
}
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
) (*types.RepositoryCore, error) {
func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session, repoRef string, reqPermission enum.Permission) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
}
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo: %w", err)
}
// repo state check happens per operation as it varies given the stage of the migration.
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("failed to verify authorization: %w", err)
}
@ -126,8 +109,8 @@ func (c *Controller) getSpaceCheckAccess(
session *auth.Session,
parentRef string,
reqPermission enum.Permission,
) (*types.SpaceCore, error) {
space, err := c.spaceFinder.FindByRef(ctx, parentRef)
) (*types.Space, error) {
space, err := c.spaceStore.FindByRef(ctx, parentRef)
if err != nil {
return nil, fmt.Errorf("parent space not found: %w", err)
}

View File

@ -17,7 +17,6 @@ package migrate
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"
@ -26,7 +25,6 @@ import (
repoCtrl "github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/bootstrap"
repoevents "github.com/harness/gitness/app/events/repo"
"github.com/harness/gitness/app/githook"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
@ -49,20 +47,15 @@ func (c *Controller) CreateRepo(
session *auth.Session,
in *CreateRepoInput,
) (*repoCtrl.RepositoryOutput, error) {
if err := c.sanitizeCreateRepoInput(in, session); err != nil {
if err := c.sanitizeCreateRepoInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
parentSpaceCore, err := c.spaceCheckAuth(ctx, session, in.ParentRef)
parentSpace, err := c.spaceCheckAuth(ctx, session, in.ParentRef)
if err != nil {
return nil, fmt.Errorf("failed to check auth in parent '%s': %w", in.ParentRef, err)
}
parentSpace, err := c.spaceStore.Find(ctx, parentSpaceCore.ID)
if err != nil {
return nil, fmt.Errorf("failed to find space by ID: %w", err)
}
// generate envars (add everything githook CLI needs for execution)
envVars, err := githook.GenerateEnvironmentVariables(
ctx,
@ -120,11 +113,9 @@ func (c *Controller) CreateRepo(
CreatedBy: session.Principal.ID,
Created: now.UnixMilli(),
Updated: now.UnixMilli(),
LastGITPush: now.UnixMilli(), // even in case of an empty repo, the git repo got created.
DefaultBranch: in.DefaultBranch,
IsEmpty: true,
State: enum.RepoStateMigrateGitPush,
Tags: json.RawMessage(`{}`),
}
return c.repoStore.Create(ctx, repo)
@ -137,8 +128,7 @@ func (c *Controller) CreateRepo(
repo.GitURL = c.urlProvider.GenerateGITCloneURL(ctx, repo.Path)
repo.GitSSHURL = c.urlProvider.GenerateGITCloneSSHURL(ctx, repo.Path)
isPublicAccessSupported, err := c.publicAccess.
IsPublicAccessSupported(ctx, enum.PublicResourceTypeRepo, parentSpace.Path)
isPublicAccessSupported, err := c.publicAccess.IsPublicAccessSupported(ctx, parentSpace.Path)
if err != nil {
return nil, fmt.Errorf(
"failed to check if public access is supported for parent space %s: %w",
@ -149,9 +139,7 @@ func (c *Controller) CreateRepo(
isRepoPublic := in.IsPublic
if !isPublicAccessSupported {
log.Ctx(ctx).Debug().Msgf(
"public access is not supported, create migrating repo %s as private instead",
repo.Identifier)
log.Debug().Msgf("public access is not supported, create migrating repo %s as private instead", repo.Identifier)
isRepoPublic = false
}
err = c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, isRepoPublic)
@ -168,20 +156,12 @@ func (c *Controller) CreateRepo(
Repository: *repo,
IsPublic: isRepoPublic,
}),
audit.WithData("created by", "migrator"),
)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("failed to insert audit log for import repository operation")
log.Warn().Msgf("failed to insert audit log for import repository operation: %s", err)
}
c.eventReporter.Created(ctx, &repoevents.CreatedPayload{
Base: repoevents.Base{
RepoID: repo.ID,
PrincipalID: session.Principal.ID,
},
IsPublic: isRepoPublic,
IsMigrated: true,
})
return &repoCtrl.RepositoryOutput{
Repository: *repo,
IsPublic: isRepoPublic,
@ -192,8 +172,8 @@ func (c *Controller) spaceCheckAuth(
ctx context.Context,
session *auth.Session,
parentRef string,
) (*types.SpaceCore, error) {
space, err := c.spaceFinder.FindByRef(ctx, parentRef)
) (*types.Space, error) {
space, err := c.spaceStore.FindByRef(ctx, parentRef)
if err != nil {
return nil, fmt.Errorf("parent space not found: %w", err)
}
@ -205,21 +185,20 @@ func (c *Controller) spaceCheckAuth(
Identifier: "",
}
if err = apiauth.Check(
ctx, c.authorizer, session, scope, resource, enum.PermissionRepoCreate,
); err != nil {
return nil, err
err = apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionRepoCreate)
if err != nil {
return nil, fmt.Errorf("auth check failed: %w", err)
}
return space, nil
}
func (c *Controller) sanitizeCreateRepoInput(in *CreateRepoInput, session *auth.Session) error {
func (c *Controller) sanitizeCreateRepoInput(in *CreateRepoInput) error {
if err := repoCtrl.ValidateParentRef(in.ParentRef); err != nil {
return err
}
if err := c.identifierCheck(in.Identifier, session); err != nil {
if err := c.identifierCheck(in.Identifier); err != nil {
return err
}

View File

@ -17,14 +17,11 @@ package migrate
import (
"context"
"fmt"
"slices"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
var validTransitions = map[enum.RepoState][]enum.RepoState{
@ -35,7 +32,6 @@ var validTransitions = map[enum.RepoState][]enum.RepoState{
type UpdateStateInput struct {
State enum.RepoState `json:"state"`
Force bool `json:"force,omitempty"`
}
func (c *Controller) UpdateRepoState(
@ -49,44 +45,26 @@ func (c *Controller) UpdateRepoState(
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}
repoFull, err := c.repoStore.Find(ctx, repo.ID)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ID: %w", err)
if !stateTransitionValid(repo, in.State) {
return nil, usererror.BadRequestf("Changing repo state from %s to %s is not allowed.", repo.State, in.State)
}
repoFull, err = c.repoStore.UpdateOptLock(ctx, repoFull, func(r *types.Repository) error {
if !stateTransitionValid(ctx, repo.Identifier, r.State, in.State, in.Force) {
return usererror.BadRequestf("Changing repo state from %s to %s is not allowed.", r.State, in.State)
}
repo, err = c.repoStore.UpdateOptLock(ctx, repo, func(r *types.Repository) error {
r.State = in.State
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to update the repo state: %w", err)
}
c.repoFinder.MarkChanged(ctx, repo)
return repoFull, nil
return repo, nil
}
func stateTransitionValid(
ctx context.Context,
repoIdentifier string,
currentState enum.RepoState,
newState enum.RepoState,
force bool,
) bool {
if slices.Contains(validTransitions[currentState], newState) {
return true
}
if force {
log.Ctx(ctx).Warn().Msgf("Forcing state transition for repo %s from %s to %s",
repoIdentifier, currentState, newState)
return true
func stateTransitionValid(repo *types.Repository, newState enum.RepoState) bool {
for _, validState := range validTransitions[repo.State] {
if validState == newState {
return true
}
}
return false

View File

@ -17,10 +17,8 @@ package migrate
import (
"github.com/harness/gitness/app/api/controller/limiter"
"github.com/harness/gitness/app/auth/authz"
repoevents "github.com/harness/gitness/app/events/repo"
"github.com/harness/gitness/app/services/migrate"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/audit"
@ -51,9 +49,6 @@ func ProvideController(
tx dbtx.Transactor,
spaceStore store.SpaceStore,
repoStore store.RepoStore,
spaceFinder refcache.SpaceFinder,
repoFinder refcache.RepoFinder,
eventReporter *repoevents.Reporter,
) *Controller {
return NewController(
authorizer,
@ -70,8 +65,5 @@ func ProvideController(
tx,
spaceStore,
repoStore,
spaceFinder,
repoFinder,
eventReporter,
)
}

View File

@ -15,70 +15,32 @@
package pipeline
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
events "github.com/harness/gitness/app/events/pipeline"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type Controller struct {
defaultBranch string
repoStore store.RepoStore
triggerStore store.TriggerStore
authorizer authz.Authorizer
pipelineStore store.PipelineStore
reporter events.Reporter
repoFinder refcache.RepoFinder
}
func NewController(
authorizer authz.Authorizer,
repoStore store.RepoStore,
triggerStore store.TriggerStore,
pipelineStore store.PipelineStore,
reporter events.Reporter,
repoFinder refcache.RepoFinder,
) *Controller {
return &Controller{
repoFinder: repoFinder,
repoStore: repoStore,
triggerStore: triggerStore,
authorizer: authorizer,
pipelineStore: pipelineStore,
reporter: reporter,
}
}
// getRepoCheckPipelineAccess fetches a repo, checks if operation is allowed given the repo state
// and checks if the current user has permission to access pipelines of the repo.
//
//nolint:unparam
func (c *Controller) getRepoCheckPipelineAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
pipelineIdentifier string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.RepositoryCore, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path,
pipelineIdentifier, reqPermission)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
return repo, nil
}

View File

@ -20,6 +20,7 @@ import (
"strings"
"time"
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"
@ -56,9 +57,14 @@ func (c *Controller) Create(
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, "", enum.PermissionPipelineEdit)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, "", enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)
}
var pipeline *types.Pipeline

View File

@ -18,6 +18,7 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
@ -28,9 +29,14 @@ func (c *Controller) Delete(
repoRef string,
identifier string,
) error {
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, identifier, enum.PermissionPipelineDelete)
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return err
return fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, identifier, enum.PermissionPipelineDelete)
if err != nil {
return fmt.Errorf("failed to authorize pipeline: %w", err)
}
err = c.pipelineStore.DeleteByIdentifier(ctx, repo.ID, identifier)

Some files were not shown because too many files have changed in this diff Show More