Compare commits
2 Commits
main
...
devcontain
| Author | SHA1 | Date |
|---|---|---|
|
|
ec4509cc1f | |
|
|
0a1d02e3ca |
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
#sudo service redis-server start
|
||||
redis-server &
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
972
.golangci.yml
972
.golangci.yml
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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}}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"dev": {
|
||||
"baseurl": "http://localhost:3000/api/v1"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
POST {{baseurl}}/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"login_identifier": "{{login_identifier}}",
|
||||
"password": "{{password}}"
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
### Get metric for space
|
||||
|
||||
GET {{baseurl}}/spaces/root/+/usage/metric
|
||||
Authorization: {{token}}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" ]
|
||||
61
Makefile
61
Makefile
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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/).
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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.",
|
||||
}
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue