mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
Compare commits
2 Commits
1255aa0c41
...
commit-ser
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e71863944 | ||
|
|
cc1f9f53f1 |
8
.github/workflows/ci-build.yaml
vendored
8
.github/workflows/ci-build.yaml
vendored
@@ -370,11 +370,11 @@ jobs:
|
||||
path: test-results
|
||||
- name: combine-go-coverage
|
||||
# We generate coverage reports for all Argo CD components, but only the applicationset-controller,
|
||||
# app-controller, and repo-server report contain coverage data. The other components currently don't shut down
|
||||
# gracefully, so no coverage data is produced. Once those components are fixed, we can add references to their
|
||||
# coverage output directories.
|
||||
# app-controller, repo-server, and commit-server report contain coverage data. The other components currently
|
||||
# don't shut down gracefully, so no coverage data is produced. Once those components are fixed, we can add
|
||||
# references to their coverage output directories.
|
||||
run: |
|
||||
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller -o test-results/full-coverage.out
|
||||
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out
|
||||
- name: Upload code coverage information to codecov.io
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
|
||||
with:
|
||||
|
||||
@@ -26,6 +26,13 @@ packages:
|
||||
github.com/argoproj/argo-cd/v2/applicationset/utils:
|
||||
interfaces:
|
||||
Renderer:
|
||||
github.com/argoproj/argo-cd/v2/commitserver/commit:
|
||||
interfaces:
|
||||
RepoClientFactory:
|
||||
github.com/argoproj/argo-cd/v2/commitserver/apiclient:
|
||||
interfaces:
|
||||
CommitServiceClient:
|
||||
Clientset:
|
||||
github.com/argoproj/argo-cd/v2/controller/cache:
|
||||
interfaces:
|
||||
LiveStateCache:
|
||||
|
||||
@@ -140,7 +140,8 @@ RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-notifications && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
|
||||
|
||||
USER $ARGOCD_USER_ID
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
|
||||
1
Makefile
1
Makefile
@@ -472,6 +472,7 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
|
||||
mkdir -p /tmp/coverage/repo-server
|
||||
mkdir -p /tmp/coverage/applicationset-controller
|
||||
mkdir -p /tmp/coverage/notification
|
||||
mkdir -p /tmp/coverage/commit-server
|
||||
# set paths for locally managed ssh known hosts and tls certs data
|
||||
ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
|
||||
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
|
||||
|
||||
1
Procfile
1
Procfile
@@ -4,6 +4,7 @@ dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/
|
||||
redis: hack/start-redis-with-password.sh
|
||||
repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/repo-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
|
||||
cmp-server: [ "$ARGOCD_E2E_TEST" = 'true' ] && exit 0 || [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-cmp-server ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} $COMMAND --config-dir-path ./test/cmp --loglevel debug --otlp-address=${ARGOCD_OTLP_ADDRESS}"
|
||||
commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/commit-server} FORCE_LOG_COLORS=1 ARGOCD_BINARY_NAME=argocd-commit-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_COMMITSERVER_PORT:-8086}"
|
||||
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
|
||||
git-server: test/fixture/testrepos/start-git.sh
|
||||
helm-registry: test/fixture/testrepos/start-helm-registry.sh
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/bradleyfalzon/ghinstallation/v2"
|
||||
"github.com/google/go-github/v63/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/go-github/v63/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package pull_request
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-github/v63/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/google/go-github/v63/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
||||
123
assets/swagger.json
generated
123
assets/swagger.json
generated
@@ -6857,6 +6857,9 @@
|
||||
"source": {
|
||||
"$ref": "#/definitions/v1alpha1ApplicationSource"
|
||||
},
|
||||
"sourceHydrator": {
|
||||
"$ref": "#/definitions/v1alpha1SourceHydrator"
|
||||
},
|
||||
"sources": {
|
||||
"type": "array",
|
||||
"title": "Sources is a reference to the location of the application's manifests or chart",
|
||||
@@ -6914,6 +6917,9 @@
|
||||
"$ref": "#/definitions/applicationv1alpha1ResourceStatus"
|
||||
}
|
||||
},
|
||||
"sourceHydrator": {
|
||||
"$ref": "#/definitions/v1alpha1SourceHydratorStatus"
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string",
|
||||
"title": "SourceType specifies the type of this application"
|
||||
@@ -7341,6 +7347,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1DrySource": {
|
||||
"description": "DrySource specifies a location for dry \"don't repeat yourself\" manifest source information.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"title": "Path is a directory path within the Git repository where the manifests are located"
|
||||
},
|
||||
"repoURL": {
|
||||
"type": "string",
|
||||
"title": "RepoURL is the URL to the git repository that contains the application manifests"
|
||||
},
|
||||
"targetRevision": {
|
||||
"type": "string",
|
||||
"title": "TargetRevision defines the revision of the source to hydrate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1DuckTypeGenerator": {
|
||||
"description": "DuckType defines a generator to match against clusters registered with ArgoCD.",
|
||||
"type": "object",
|
||||
@@ -7595,6 +7619,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1HydrateOperation": {
|
||||
"type": "object",
|
||||
"title": "HydrateOperation contains information about the most recent hydrate operation",
|
||||
"properties": {
|
||||
"drySHA": {
|
||||
"type": "string",
|
||||
"title": "DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation"
|
||||
},
|
||||
"finishedAt": {
|
||||
"$ref": "#/definitions/v1Time"
|
||||
},
|
||||
"hydratedSHA": {
|
||||
"type": "string",
|
||||
"title": "HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message contains a message describing the current status of the hydrate operation"
|
||||
},
|
||||
"phase": {
|
||||
"type": "string",
|
||||
"title": "Phase indicates the status of the hydrate operation"
|
||||
},
|
||||
"sourceHydrator": {
|
||||
"$ref": "#/definitions/v1alpha1SourceHydrator"
|
||||
},
|
||||
"startedAt": {
|
||||
"$ref": "#/definitions/v1Time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1HydrateTo": {
|
||||
"description": "HydrateTo specifies a location to which hydrated manifests should be pushed as a \"staging area\" before being moved to\nthe SyncSource. The RepoURL and Path are assumed based on the associated SyncSource config in the SourceHydrator.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"targetBranch": {
|
||||
"type": "string",
|
||||
"title": "TargetBranch is the branch to which hydrated manifests should be committed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9202,6 +9267,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1SourceHydrator": {
|
||||
"description": "SourceHydrator specifies a dry \"don't repeat yourself\" source for manifests, a sync source from which to sync\nhydrated manifests, and an optional hydrateTo location to act as a \"staging\" aread for hydrated manifests.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"drySource": {
|
||||
"$ref": "#/definitions/v1alpha1DrySource"
|
||||
},
|
||||
"hydrateTo": {
|
||||
"$ref": "#/definitions/v1alpha1HydrateTo"
|
||||
},
|
||||
"syncSource": {
|
||||
"$ref": "#/definitions/v1alpha1SyncSource"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1SourceHydratorStatus": {
|
||||
"type": "object",
|
||||
"title": "SourceHydratorStatus contains information about the current state of source hydration",
|
||||
"properties": {
|
||||
"currentOperation": {
|
||||
"$ref": "#/definitions/v1alpha1HydrateOperation"
|
||||
},
|
||||
"lastSuccessfulOperation": {
|
||||
"$ref": "#/definitions/v1alpha1SuccessfulHydrateOperation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1SuccessfulHydrateOperation": {
|
||||
"type": "object",
|
||||
"title": "SuccessfulHydrateOperation contains information about the most recent successful hydrate operation",
|
||||
"properties": {
|
||||
"drySHA": {
|
||||
"type": "string",
|
||||
"title": "DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation"
|
||||
},
|
||||
"hydratedSHA": {
|
||||
"type": "string",
|
||||
"title": "HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation"
|
||||
},
|
||||
"sourceHydrator": {
|
||||
"$ref": "#/definitions/v1alpha1SourceHydrator"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1SyncOperation": {
|
||||
"description": "SyncOperation contains details about a sync operation.",
|
||||
"type": "object",
|
||||
@@ -9361,6 +9470,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1SyncSource": {
|
||||
"description": "SyncSource specifies a location from which hydrated manifests may be synced. RepoURL is assumed based on the\nassociated DrySource config in the SourceHydrator.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"description": "Path is a directory path within the git repository where hydrated manifests should be committed to and synced\nfrom. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetBranch": {
|
||||
"type": "string",
|
||||
"title": "TargetBranch is the branch to which hydrated manifests should be committed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1alpha1SyncStatus": {
|
||||
"type": "object",
|
||||
"title": "SyncStatus contains information about the currently observed live and desired states of an application",
|
||||
|
||||
117
cmd/argocd-commit-server/commands/argocd_commit_server.go
Normal file
117
cmd/argocd-commit-server/commands/argocd_commit_server.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/util/askpass"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
"github.com/argoproj/argo-cd/v2/util/healthz"
|
||||
ioutil "github.com/argoproj/argo-cd/v2/util/io"
|
||||
)
|
||||
|
||||
// NewCommand returns a new instance of an argocd-commit-server command
|
||||
func NewCommand() *cobra.Command {
|
||||
var (
|
||||
listenHost string
|
||||
listenPort int
|
||||
metricsPort int
|
||||
metricsHost string
|
||||
)
|
||||
command := &cobra.Command{
|
||||
Use: "argocd-commit-server",
|
||||
Short: "Run Argo CD Commit Server",
|
||||
Long: "Argo CD Commit Server is an internal service which commits and pushes hydrated manifests to git. This command runs Commit Server in the foreground.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
vers := common.GetVersion()
|
||||
vers.LogStartupInfo(
|
||||
"Argo CD Commit Server",
|
||||
map[string]any{
|
||||
"port": listenPort,
|
||||
},
|
||||
)
|
||||
|
||||
cli.SetLogFormat(cmdutil.LogFormat)
|
||||
cli.SetLogLevel(cmdutil.LogLevel)
|
||||
|
||||
metricsServer := metrics.NewMetricsServer()
|
||||
http.Handle("/metrics", metricsServer.GetHandler())
|
||||
go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf("%s:%d", metricsHost, metricsPort), nil)) }()
|
||||
|
||||
askPassServer := askpass.NewServer(askpass.CommitServerSocketPath)
|
||||
go func() { errors.CheckError(askPassServer.Run()) }()
|
||||
|
||||
server := commitserver.NewServer(askPassServer, metricsServer)
|
||||
grpc := server.CreateGRPC()
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", listenHost, listenPort))
|
||||
errors.CheckError(err)
|
||||
|
||||
healthz.ServeHealthCheck(http.DefaultServeMux, func(r *http.Request) error {
|
||||
if val, ok := r.URL.Query()["full"]; ok && len(val) > 0 && val[0] == "true" {
|
||||
// connect to itself to make sure commit server is able to serve connection
|
||||
// used by liveness probe to auto restart commit server
|
||||
conn, err := apiclient.NewConnection(fmt.Sprintf("localhost:%d", listenPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ioutil.Close(conn)
|
||||
client := grpc_health_v1.NewHealthClient(conn)
|
||||
res, err := client.Check(r.Context(), &grpc_health_v1.HealthCheckRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Status != grpc_health_v1.HealthCheckResponse_SERVING {
|
||||
return fmt.Errorf("grpc health check status is '%v'", res.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Graceful shutdown code adapted from here: https://gist.github.com/embano1/e0bf49d24f1cdd07cffad93097c04f0a
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s := <-sigCh
|
||||
log.Printf("got signal %v, attempting graceful shutdown", s)
|
||||
grpc.GracefulStop()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
log.Println("starting grpc server")
|
||||
err = grpc.Serve(listener)
|
||||
errors.CheckError(err)
|
||||
wg.Wait()
|
||||
log.Println("clean shutdown")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_COMMIT_SERVER_LOGFORMAT", "text"), "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_COMMIT_SERVER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_COMMIT_SERVER_LISTEN_ADDRESS", common.DefaultAddressCommitServer), "Listen on given address for incoming connections")
|
||||
command.Flags().IntVar(&listenPort, "port", common.DefaultPortCommitServer, "Listen on given port for incoming connections")
|
||||
command.Flags().StringVar(&metricsHost, "metrics-address", env.StringFromEnv("ARGOCD_COMMIT_SERVER_METRICS_LISTEN_ADDRESS", common.DefaultAddressCommitServerMetrics), "Listen on given address for metrics")
|
||||
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortCommitServerMetrics, "Start metrics server on given port")
|
||||
|
||||
return command
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/askpass"
|
||||
"github.com/argoproj/argo-cd/v2/util/askpass"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
|
||||
"github.com/argoproj/argo-cd/v2/util/io"
|
||||
|
||||
@@ -23,10 +23,10 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/askpass"
|
||||
reposervercache "github.com/argoproj/argo-cd/v2/reposerver/cache"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/repository"
|
||||
"github.com/argoproj/argo-cd/v2/util/askpass"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
appcontroller "github.com/argoproj/argo-cd/v2/cmd/argocd-application-controller/commands"
|
||||
applicationset "github.com/argoproj/argo-cd/v2/cmd/argocd-applicationset-controller/commands"
|
||||
cmpserver "github.com/argoproj/argo-cd/v2/cmd/argocd-cmp-server/commands"
|
||||
commitserver "github.com/argoproj/argo-cd/v2/cmd/argocd-commit-server/commands"
|
||||
dex "github.com/argoproj/argo-cd/v2/cmd/argocd-dex/commands"
|
||||
gitaskpass "github.com/argoproj/argo-cd/v2/cmd/argocd-git-ask-pass/commands"
|
||||
k8sauth "github.com/argoproj/argo-cd/v2/cmd/argocd-k8s-auth/commands"
|
||||
@@ -46,6 +47,8 @@ func main() {
|
||||
case "argocd-cmp-server":
|
||||
command = cmpserver.NewCommand()
|
||||
isCLI = true
|
||||
case "argocd-commit-server":
|
||||
command = commitserver.NewCommand()
|
||||
case "argocd-dex":
|
||||
command = dex.NewCommand()
|
||||
case "argocd-notifications":
|
||||
|
||||
49
commitserver/apiclient/clientset.go
Normal file
49
commitserver/apiclient/clientset.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package apiclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/io"
|
||||
)
|
||||
|
||||
// Clientset represents commit server api clients
|
||||
type Clientset interface {
|
||||
NewCommitServerClient() (io.Closer, CommitServiceClient, error)
|
||||
}
|
||||
|
||||
type clientSet struct {
|
||||
address string
|
||||
}
|
||||
|
||||
// NewCommitServerClient creates new instance of commit server client
|
||||
func (c *clientSet) NewCommitServerClient() (io.Closer, CommitServiceClient, error) {
|
||||
conn, err := NewConnection(c.address)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to open a new connection to commit server: %w", err)
|
||||
}
|
||||
return conn, NewCommitServiceClient(conn), nil
|
||||
}
|
||||
|
||||
// NewConnection creates new connection to commit server
|
||||
func NewConnection(address string) (*grpc.ClientConn, error) {
|
||||
var opts []grpc.DialOption
|
||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
|
||||
// TODO: switch to grpc.NewClient.
|
||||
// nolint:staticcheck
|
||||
conn, err := grpc.Dial(address, opts...)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to connect to commit service with address %s", address)
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// NewCommitServerClientset creates new instance of commit server Clientset
|
||||
func NewCommitServerClientset(address string) Clientset {
|
||||
return &clientSet{address: address}
|
||||
}
|
||||
1382
commitserver/apiclient/commit.pb.go
generated
Normal file
1382
commitserver/apiclient/commit.pb.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
68
commitserver/apiclient/mocks/Clientset.go
generated
Normal file
68
commitserver/apiclient/mocks/Clientset.go
generated
Normal file
@@ -0,0 +1,68 @@
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
apiclient "github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
io "github.com/argoproj/argo-cd/v2/util/io"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Clientset is an autogenerated mock type for the Clientset type
|
||||
type Clientset struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// NewCommitServerClient provides a mock function with given fields:
|
||||
func (_m *Clientset) NewCommitServerClient() (io.Closer, apiclient.CommitServiceClient, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for NewCommitServerClient")
|
||||
}
|
||||
|
||||
var r0 io.Closer
|
||||
var r1 apiclient.CommitServiceClient
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func() (io.Closer, apiclient.CommitServiceClient, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() io.Closer); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(io.Closer)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() apiclient.CommitServiceClient); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(apiclient.CommitServiceClient)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func() error); ok {
|
||||
r2 = rf()
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// NewClientset creates a new instance of Clientset. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewClientset(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Clientset {
|
||||
mock := &Clientset{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
69
commitserver/apiclient/mocks/CommitServiceClient.go
generated
Normal file
69
commitserver/apiclient/mocks/CommitServiceClient.go
generated
Normal file
@@ -0,0 +1,69 @@
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
apiclient "github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// CommitServiceClient is an autogenerated mock type for the CommitServiceClient type
|
||||
type CommitServiceClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CommitHydratedManifests provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *CommitServiceClient) CommitHydratedManifests(ctx context.Context, in *apiclient.CommitHydratedManifestsRequest, opts ...grpc.CallOption) (*apiclient.CommitHydratedManifestsResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CommitHydratedManifests")
|
||||
}
|
||||
|
||||
var r0 *apiclient.CommitHydratedManifestsResponse
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *apiclient.CommitHydratedManifestsRequest, ...grpc.CallOption) (*apiclient.CommitHydratedManifestsResponse, error)); ok {
|
||||
return rf(ctx, in, opts...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *apiclient.CommitHydratedManifestsRequest, ...grpc.CallOption) *apiclient.CommitHydratedManifestsResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*apiclient.CommitHydratedManifestsResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *apiclient.CommitHydratedManifestsRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewCommitServiceClient creates a new instance of CommitServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewCommitServiceClient(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *CommitServiceClient {
|
||||
mock := &CommitServiceClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
225
commitserver/commit/commit.go
Normal file
225
commitserver/commit/commit.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
"github.com/argoproj/argo-cd/v2/util/io/files"
|
||||
)
|
||||
|
||||
// Service is the service that handles commit requests.
|
||||
type Service struct {
|
||||
gitCredsStore git.CredsStore
|
||||
metricsServer *metrics.Server
|
||||
repoClientFactory RepoClientFactory
|
||||
}
|
||||
|
||||
// NewService returns a new instance of the commit service.
|
||||
func NewService(gitCredsStore git.CredsStore, metricsServer *metrics.Server) *Service {
|
||||
return &Service{
|
||||
gitCredsStore: gitCredsStore,
|
||||
metricsServer: metricsServer,
|
||||
repoClientFactory: NewRepoClientFactory(gitCredsStore, metricsServer),
|
||||
}
|
||||
}
|
||||
|
||||
// CommitHydratedManifests handles a commit request. It clones the repository, checks out the sync branch, checks out
|
||||
// the target branch, clears the repository contents, writes the manifests to the repository, commits the changes, and
|
||||
// pushes the changes. It returns the hydrated revision SHA and an error if one occurred.
|
||||
func (s *Service) CommitHydratedManifests(ctx context.Context, r *apiclient.CommitHydratedManifestsRequest) (*apiclient.CommitHydratedManifestsResponse, error) {
|
||||
// This method is intentionally short. It's a wrapper around handleCommitRequest that adds metrics and logging.
|
||||
// Keep logic here minimal and put most of the logic in handleCommitRequest.
|
||||
startTime := time.Now()
|
||||
|
||||
// We validate for a nil repo in handleCommitRequest, but we need to check for a nil repo here to get the repo URL
|
||||
// for metrics.
|
||||
var repoURL string
|
||||
if r.Repo != nil {
|
||||
repoURL = r.Repo.Repo
|
||||
}
|
||||
|
||||
var err error
|
||||
s.metricsServer.IncPendingCommitRequest(repoURL)
|
||||
defer func() {
|
||||
s.metricsServer.DecPendingCommitRequest(repoURL)
|
||||
commitResponseType := metrics.CommitResponseTypeSuccess
|
||||
if err != nil {
|
||||
commitResponseType = metrics.CommitResponseTypeFailure
|
||||
}
|
||||
s.metricsServer.IncCommitRequest(repoURL, commitResponseType)
|
||||
s.metricsServer.ObserveCommitRequestDuration(repoURL, commitResponseType, time.Since(startTime))
|
||||
}()
|
||||
|
||||
logCtx := log.WithFields(log.Fields{"branch": r.TargetBranch, "drySHA": r.DrySha})
|
||||
|
||||
out, sha, err := s.handleCommitRequest(logCtx, r)
|
||||
if err != nil {
|
||||
logCtx.WithError(err).WithField("output", out).Error("failed to handle commit request")
|
||||
|
||||
// No need to wrap this error, sufficient context is build in handleCommitRequest.
|
||||
return &apiclient.CommitHydratedManifestsResponse{}, err
|
||||
}
|
||||
|
||||
logCtx.Info("Successfully handled commit request")
|
||||
return &apiclient.CommitHydratedManifestsResponse{
|
||||
HydratedSha: sha,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleCommitRequest handles the commit request. It clones the repository, checks out the sync branch, checks out the
|
||||
// target branch, clears the repository contents, writes the manifests to the repository, commits the changes, and pushes
|
||||
// the changes. It returns the output of the git commands and an error if one occurred.
|
||||
func (s *Service) handleCommitRequest(logCtx *log.Entry, r *apiclient.CommitHydratedManifestsRequest) (string, string, error) {
|
||||
if r.Repo == nil {
|
||||
return "", "", fmt.Errorf("repo is required")
|
||||
}
|
||||
if r.Repo.Repo == "" {
|
||||
return "", "", fmt.Errorf("repo URL is required")
|
||||
}
|
||||
if r.TargetBranch == "" {
|
||||
return "", "", fmt.Errorf("target branch is required")
|
||||
}
|
||||
if r.SyncBranch == "" {
|
||||
return "", "", fmt.Errorf("sync branch is required")
|
||||
}
|
||||
|
||||
logCtx = logCtx.WithField("repo", r.Repo.Repo)
|
||||
logCtx.Debug("Initiating git client")
|
||||
gitClient, dirPath, cleanup, err := s.initGitClient(logCtx, r)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to init git client: %w", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
logCtx.Debugf("Checking out sync branch %s", r.SyncBranch)
|
||||
var out string
|
||||
out, err = gitClient.CheckoutOrOrphan(r.SyncBranch, false)
|
||||
if err != nil {
|
||||
return out, "", fmt.Errorf("failed to checkout sync branch: %w", err)
|
||||
}
|
||||
|
||||
logCtx.Debugf("Checking out target branch %s", r.TargetBranch)
|
||||
out, err = gitClient.CheckoutOrNew(r.TargetBranch, r.SyncBranch, false)
|
||||
if err != nil {
|
||||
return out, "", fmt.Errorf("failed to checkout target branch: %w", err)
|
||||
}
|
||||
|
||||
logCtx.Debug("Clearing repo contents")
|
||||
out, err = gitClient.RemoveContents()
|
||||
if err != nil {
|
||||
return out, "", fmt.Errorf("failed to clear repo: %w", err)
|
||||
}
|
||||
|
||||
logCtx.Debug("Writing manifests")
|
||||
err = WriteForPaths(dirPath, r.Repo.Repo, r.DrySha, r.Paths)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to write manifests: %w", err)
|
||||
}
|
||||
|
||||
logCtx.Debug("Committing and pushing changes")
|
||||
out, err = gitClient.CommitAndPush(r.TargetBranch, r.CommitMessage)
|
||||
if err != nil {
|
||||
return out, "", fmt.Errorf("failed to commit and push: %w", err)
|
||||
}
|
||||
|
||||
logCtx.Debug("Getting commit SHA")
|
||||
sha, err := gitClient.CommitSHA()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get commit SHA: %w", err)
|
||||
}
|
||||
|
||||
return "", sha, nil
|
||||
}
|
||||
|
||||
// initGitClient initializes a git client for the given repository and returns the client, the path to the directory where
|
||||
// the repository is cloned, a cleanup function that should be called when the directory is no longer needed, and an error
|
||||
// if one occurred.
|
||||
func (s *Service) initGitClient(logCtx *log.Entry, r *apiclient.CommitHydratedManifestsRequest) (git.Client, string, func(), error) {
|
||||
dirPath, err := files.CreateTempDir("/tmp/_commit-service")
|
||||
if err != nil {
|
||||
return nil, "", nil, fmt.Errorf("failed to create temp dir: %w", err)
|
||||
}
|
||||
// Call cleanupOrLog in this function if an error occurs to ensure the temp dir is cleaned up.
|
||||
cleanupOrLog := func() {
|
||||
err := os.RemoveAll(dirPath)
|
||||
if err != nil {
|
||||
logCtx.WithError(err).Error("failed to cleanup temp dir")
|
||||
}
|
||||
}
|
||||
|
||||
gitClient, err := s.repoClientFactory.NewClient(r.Repo, dirPath)
|
||||
if err != nil {
|
||||
cleanupOrLog()
|
||||
return nil, "", nil, fmt.Errorf("failed to create git client: %w", err)
|
||||
}
|
||||
|
||||
logCtx.Debugf("Initializing repo %s", r.Repo.Repo)
|
||||
err = gitClient.Init()
|
||||
if err != nil {
|
||||
cleanupOrLog()
|
||||
return nil, "", nil, fmt.Errorf("failed to init git client: %w", err)
|
||||
}
|
||||
|
||||
logCtx.Debugf("Fetching repo %s", r.Repo.Repo)
|
||||
err = gitClient.Fetch("")
|
||||
if err != nil {
|
||||
cleanupOrLog()
|
||||
return nil, "", nil, fmt.Errorf("failed to clone repo: %w", err)
|
||||
}
|
||||
|
||||
// FIXME: make it work for GHE
|
||||
//logCtx.Debugf("Getting user info for repo credentials")
|
||||
//gitCreds := r.Repo.GetGitCreds(s.gitCredsStore)
|
||||
//startTime := time.Now()
|
||||
//authorName, authorEmail, err := gitCreds.GetUserInfo(ctx)
|
||||
//s.metricsServer.ObserveUserInfoRequestDuration(r.Repo.Repo, getCredentialType(r.Repo), time.Since(startTime))
|
||||
//if err != nil {
|
||||
// cleanupOrLog()
|
||||
// return nil, "", nil, fmt.Errorf("failed to get github app info: %w", err)
|
||||
//}
|
||||
var authorName, authorEmail string
|
||||
|
||||
if authorName == "" {
|
||||
authorName = "Argo CD"
|
||||
}
|
||||
if authorEmail == "" {
|
||||
logCtx.Warnf("Author email not available, using 'argo-cd@example.com'.")
|
||||
authorEmail = "argo-cd@example.com"
|
||||
}
|
||||
|
||||
logCtx.Debugf("Setting author %s <%s>", authorName, authorEmail)
|
||||
_, err = gitClient.SetAuthor(authorName, authorEmail)
|
||||
if err != nil {
|
||||
cleanupOrLog()
|
||||
return nil, "", nil, fmt.Errorf("failed to set author: %w", err)
|
||||
}
|
||||
|
||||
return gitClient, dirPath, cleanupOrLog, nil
|
||||
}
|
||||
|
||||
type hydratorMetadataFile struct {
|
||||
RepoURL string `json:"repoURL"`
|
||||
DrySHA string `json:"drySha"`
|
||||
Commands []string `json:"commands"`
|
||||
}
|
||||
|
||||
// TODO: make this configurable via ConfigMap.
|
||||
var manifestHydrationReadmeTemplate = `
|
||||
# Manifest Hydration
|
||||
|
||||
To hydrate the manifests in this repository, run the following commands:
|
||||
|
||||
` + "```shell\n" + `
|
||||
git clone {{ .RepoURL }}
|
||||
# cd into the cloned directory
|
||||
git checkout {{ .DrySHA }}
|
||||
{{ range $command := .Commands -}}
|
||||
{{ $command }}
|
||||
{{ end -}}` + "```"
|
||||
50
commitserver/commit/commit.proto
Normal file
50
commitserver/commit/commit.proto
Normal file
@@ -0,0 +1,50 @@
|
||||
syntax = "proto3";
|
||||
option go_package = "github.com/argoproj/argo-cd/v2/commitserver/apiclient";
|
||||
|
||||
import "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1/generated.proto";
|
||||
|
||||
// CommitHydratedManifestsRequest is the request to commit hydrated manifests to a repository.
|
||||
message CommitHydratedManifestsRequest {
|
||||
// Repo contains repository information including, at minimum, the URL of the repository. Generally it will contain
|
||||
// repo credentials.
|
||||
github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.Repository repo = 1;
|
||||
// SyncBranch is the branch Argo CD syncs from, i.e. the hydrated branch.
|
||||
string syncBranch = 2;
|
||||
// TargetBranch is the branch Argo CD is committing to, i.e. the branch that will be updated.
|
||||
string targetBranch = 3;
|
||||
// DrySha is the commit SHA from the dry branch, i.e. pre-rendered manifest branch.
|
||||
string drySha = 4;
|
||||
// CommitMessage is the commit message to use when committing changes.
|
||||
string commitMessage = 5;
|
||||
// Paths contains the paths to write hydrated manifests to, along with the manifests and commands to execute.
|
||||
repeated PathDetails paths = 6;
|
||||
}
|
||||
|
||||
// PathDetails holds information about hydrated manifests to be written to a particular path in the hydrated manifests
|
||||
// commit.
|
||||
message PathDetails {
|
||||
// Path is the path to write the hydrated manifests to.
|
||||
string path = 1;
|
||||
// Manifests contains the manifests to write to the path.
|
||||
repeated HydratedManifestDetails manifests = 2;
|
||||
// Commands contains the commands executed when hydrating the manifests.
|
||||
repeated string commands = 3;
|
||||
}
|
||||
|
||||
// ManifestDetails contains the hydrated manifests.
|
||||
message HydratedManifestDetails {
|
||||
// ManifestJSON is the hydrated manifest as JSON.
|
||||
string manifestJSON = 1;
|
||||
}
|
||||
|
||||
// ManifestsResponse is the response to the ManifestsRequest.
|
||||
message CommitHydratedManifestsResponse {
|
||||
// HydratedSha is the commit SHA of the hydrated manifests commit.
|
||||
string hydratedSha = 1;
|
||||
}
|
||||
|
||||
// CommitService is the service for committing hydrated manifests to a repository.
|
||||
service CommitService {
|
||||
// Commit commits hydrated manifests to a repository.
|
||||
rpc CommitHydratedManifests (CommitHydratedManifestsRequest) returns (CommitHydratedManifestsResponse);
|
||||
}
|
||||
125
commitserver/commit/commit_test.go
Normal file
125
commitserver/commit/commit_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/commit/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
gitmocks "github.com/argoproj/argo-cd/v2/util/git/mocks"
|
||||
)
|
||||
|
||||
func Test_CommitHydratedManifests(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validRequest := &apiclient.CommitHydratedManifestsRequest{
|
||||
Repo: &v1alpha1.Repository{
|
||||
Repo: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
},
|
||||
TargetBranch: "main",
|
||||
SyncBranch: "env/test",
|
||||
CommitMessage: "test commit message",
|
||||
}
|
||||
|
||||
t.Run("missing repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service, _ := newServiceWithMocks(t)
|
||||
request := &apiclient.CommitHydratedManifestsRequest{}
|
||||
_, err := service.CommitHydratedManifests(context.Background(), request)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "repo is required")
|
||||
})
|
||||
|
||||
t.Run("missing repo URL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service, _ := newServiceWithMocks(t)
|
||||
request := &apiclient.CommitHydratedManifestsRequest{
|
||||
Repo: &v1alpha1.Repository{},
|
||||
}
|
||||
_, err := service.CommitHydratedManifests(context.Background(), request)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "repo URL is required")
|
||||
})
|
||||
|
||||
t.Run("missing target branch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service, _ := newServiceWithMocks(t)
|
||||
request := &apiclient.CommitHydratedManifestsRequest{
|
||||
Repo: &v1alpha1.Repository{
|
||||
Repo: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
},
|
||||
}
|
||||
_, err := service.CommitHydratedManifests(context.Background(), request)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "target branch is required")
|
||||
})
|
||||
|
||||
t.Run("missing sync branch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service, _ := newServiceWithMocks(t)
|
||||
request := &apiclient.CommitHydratedManifestsRequest{
|
||||
Repo: &v1alpha1.Repository{
|
||||
Repo: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
},
|
||||
TargetBranch: "main",
|
||||
}
|
||||
_, err := service.CommitHydratedManifests(context.Background(), request)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "sync branch is required")
|
||||
})
|
||||
|
||||
t.Run("failed to create git client", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service, mockRepoClientFactory := newServiceWithMocks(t)
|
||||
mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(nil, assert.AnError).Once()
|
||||
|
||||
_, err := service.CommitHydratedManifests(context.Background(), validRequest)
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, assert.AnError)
|
||||
})
|
||||
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service, mockRepoClientFactory := newServiceWithMocks(t)
|
||||
mockGitClient := gitmocks.NewClient(t)
|
||||
mockGitClient.On("Init").Return(nil).Once()
|
||||
mockGitClient.On("Fetch", mock.Anything).Return(nil).Once()
|
||||
mockGitClient.On("SetAuthor", "Argo CD", "argo-cd@example.com").Return("", nil).Once()
|
||||
mockGitClient.On("CheckoutOrOrphan", "env/test", false).Return("", nil).Once()
|
||||
mockGitClient.On("CheckoutOrNew", "main", "env/test", false).Return("", nil).Once()
|
||||
mockGitClient.On("RemoveContents").Return("", nil).Once()
|
||||
mockGitClient.On("CommitAndPush", "main", "test commit message").Return("", nil).Once()
|
||||
mockGitClient.On("CommitSHA").Return("it-worked!", nil).Once()
|
||||
mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(mockGitClient, nil).Once()
|
||||
|
||||
resp, err := service.CommitHydratedManifests(context.Background(), validRequest)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.Equal(t, "it-worked!", resp.HydratedSha)
|
||||
})
|
||||
}
|
||||
|
||||
func newServiceWithMocks(t *testing.T) (*Service, *mocks.RepoClientFactory) {
|
||||
t.Helper()
|
||||
|
||||
metricsServer := metrics.NewMetricsServer()
|
||||
mockCredsStore := git.NoopCredsStore{}
|
||||
service := NewService(mockCredsStore, metricsServer)
|
||||
mockRepoClientFactory := mocks.NewRepoClientFactory(t)
|
||||
service.repoClientFactory = mockRepoClientFactory
|
||||
|
||||
return service, mockRepoClientFactory
|
||||
}
|
||||
23
commitserver/commit/credentialtypehelper.go
Normal file
23
commitserver/commit/credentialtypehelper.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package commit
|
||||
|
||||
import "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
// getCredentialType returns the type of credential used by the repository.
|
||||
func getCredentialType(repo *v1alpha1.Repository) string {
|
||||
if repo == nil {
|
||||
return ""
|
||||
}
|
||||
if repo.Password != "" {
|
||||
return "https"
|
||||
}
|
||||
if repo.SSHPrivateKey != "" {
|
||||
return "ssh"
|
||||
}
|
||||
if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 && repo.GithubAppInstallationId != 0 {
|
||||
return "github-app"
|
||||
}
|
||||
if repo.GCPServiceAccountKey != "" {
|
||||
return "cloud-source-repositories"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
62
commitserver/commit/credentialtypehelper_test.go
Normal file
62
commitserver/commit/credentialtypehelper_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func TestRepository_GetCredentialType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repo *v1alpha1.Repository
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Empty Repository",
|
||||
repo: nil,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "HTTPS Repository",
|
||||
repo: &v1alpha1.Repository{
|
||||
Repo: "foo",
|
||||
Password: "some-password",
|
||||
},
|
||||
want: "https",
|
||||
},
|
||||
{
|
||||
name: "SSH Repository",
|
||||
repo: &v1alpha1.Repository{
|
||||
Repo: "foo",
|
||||
SSHPrivateKey: "some-key",
|
||||
},
|
||||
want: "ssh",
|
||||
},
|
||||
{
|
||||
name: "GitHub App Repository",
|
||||
repo: &v1alpha1.Repository{
|
||||
Repo: "foo",
|
||||
GithubAppPrivateKey: "some-key",
|
||||
GithubAppId: 1,
|
||||
GithubAppInstallationId: 1,
|
||||
},
|
||||
want: "github-app",
|
||||
},
|
||||
{
|
||||
name: "Google Cloud Repository",
|
||||
repo: &v1alpha1.Repository{
|
||||
Repo: "foo",
|
||||
GCPServiceAccountKey: "some-key",
|
||||
},
|
||||
want: "cloud-source-repositories",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getCredentialType(tt.repo); got != tt.want {
|
||||
t.Errorf("Repository.GetCredentialType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
145
commitserver/commit/hydratorhelper.go
Normal file
145
commitserver/commit/hydratorhelper.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"text/template"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/io/files"
|
||||
)
|
||||
|
||||
// WriteForPaths writes the manifests, hydrator.metadata, and README.md files for each path in the provided paths. It
|
||||
// also writes a root-level hydrator.metadata file containing the repo URL and dry SHA.
|
||||
func WriteForPaths(rootPath string, repoUrl string, drySha string, paths []*apiclient.PathDetails) error {
|
||||
// Write the top-level readme.
|
||||
err := writeMetadata(rootPath, hydratorMetadataFile{DrySHA: drySha, RepoURL: repoUrl})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write top-level hydrator metadata: %w", err)
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
hydratePath := p.Path
|
||||
if hydratePath == "." {
|
||||
hydratePath = ""
|
||||
}
|
||||
|
||||
var fullHydratePath string
|
||||
fullHydratePath, err = files.SecureMkdirAll(rootPath, hydratePath, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create path: %w", err)
|
||||
}
|
||||
|
||||
// Write the manifests
|
||||
err = writeManifests(fullHydratePath, p.Manifests)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write manifests: %w", err)
|
||||
}
|
||||
|
||||
// Write hydrator.metadata containing information about the hydration process.
|
||||
hydratorMetadata := hydratorMetadataFile{
|
||||
Commands: p.Commands,
|
||||
DrySHA: drySha,
|
||||
RepoURL: repoUrl,
|
||||
}
|
||||
err = writeMetadata(fullHydratePath, hydratorMetadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write hydrator metadata: %w", err)
|
||||
}
|
||||
|
||||
// Write README
|
||||
err = writeReadme(fullHydratePath, hydratorMetadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write readme: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeMetadata writes the metadata to the hydrator.metadata file.
|
||||
func writeMetadata(dirPath string, metadata hydratorMetadataFile) error {
|
||||
hydratorMetadataJson, err := json.MarshalIndent(metadata, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal hydrator metadata: %w", err)
|
||||
}
|
||||
// No need to use SecureJoin here, as the path is already sanitized.
|
||||
hydratorMetadataPath := path.Join(dirPath, "hydrator.metadata")
|
||||
err = os.WriteFile(hydratorMetadataPath, hydratorMetadataJson, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write hydrator metadata: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeReadme writes the readme to the README.md file.
|
||||
func writeReadme(dirPath string, metadata hydratorMetadataFile) error {
|
||||
readmeTemplate := template.New("readme")
|
||||
readmeTemplate, err := readmeTemplate.Parse(manifestHydrationReadmeTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse readme template: %w", err)
|
||||
}
|
||||
// Create writer to template into
|
||||
// No need to use SecureJoin here, as the path is already sanitized.
|
||||
readmePath := path.Join(dirPath, "README.md")
|
||||
readmeFile, err := os.Create(readmePath)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("failed to create README file: %w", err)
|
||||
}
|
||||
err = readmeTemplate.Execute(readmeFile, metadata)
|
||||
closeErr := readmeFile.Close()
|
||||
if closeErr != nil {
|
||||
log.WithError(closeErr).Error("failed to close README file")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute readme template: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeManifests writes the manifests to the manifest.yaml file, truncating the file if it exists and appending the
|
||||
// manifests in the order they are provided.
|
||||
func writeManifests(dirPath string, manifests []*apiclient.HydratedManifestDetails) error {
|
||||
// If the file exists, truncate it.
|
||||
// No need to use SecureJoin here, as the path is already sanitized.
|
||||
manifestPath := path.Join(dirPath, "manifest.yaml")
|
||||
|
||||
file, err := os.OpenFile(manifestPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open manifest file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to close file")
|
||||
}
|
||||
}()
|
||||
|
||||
enc := yaml.NewEncoder(file)
|
||||
defer func() {
|
||||
err := enc.Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to close yaml encoder")
|
||||
}
|
||||
}()
|
||||
enc.SetIndent(2)
|
||||
|
||||
for _, m := range manifests {
|
||||
obj := &unstructured.Unstructured{}
|
||||
err = json.Unmarshal([]byte(m.ManifestJSON), obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal manifest: %w", err)
|
||||
}
|
||||
err = enc.Encode(&obj.Object)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode manifest: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
135
commitserver/commit/hydratorhelper_test.go
Normal file
135
commitserver/commit/hydratorhelper_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
)
|
||||
|
||||
func TestWriteForPaths(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
repoUrl := "https://github.com/example/repo"
|
||||
drySha := "abc123"
|
||||
paths := []*apiclient.PathDetails{
|
||||
{
|
||||
Path: "path1",
|
||||
Manifests: []*apiclient.HydratedManifestDetails{
|
||||
{ManifestJSON: `{"kind":"Pod","apiVersion":"v1"}`},
|
||||
},
|
||||
Commands: []string{"command1", "command2"},
|
||||
},
|
||||
{
|
||||
Path: "path2",
|
||||
Manifests: []*apiclient.HydratedManifestDetails{
|
||||
{ManifestJSON: `{"kind":"Service","apiVersion":"v1"}`},
|
||||
},
|
||||
Commands: []string{"command3"},
|
||||
},
|
||||
}
|
||||
|
||||
err := WriteForPaths(dir, repoUrl, drySha, paths)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check if the top-level hydrator.metadata exists and contains the repo URL and dry SHA
|
||||
topMetadataPath := path.Join(dir, "hydrator.metadata")
|
||||
topMetadataBytes, err := os.ReadFile(topMetadataPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
var topMetadata hydratorMetadataFile
|
||||
err = json.Unmarshal(topMetadataBytes, &topMetadata)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, repoUrl, topMetadata.RepoURL)
|
||||
assert.Equal(t, drySha, topMetadata.DrySHA)
|
||||
|
||||
for _, p := range paths {
|
||||
fullHydratePath, err := securejoin.SecureJoin(dir, p.Path)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check if each path directory exists
|
||||
assert.DirExists(t, fullHydratePath)
|
||||
|
||||
// Check if each path contains a hydrator.metadata file and contains the repo URL
|
||||
metadataPath := path.Join(fullHydratePath, "hydrator.metadata")
|
||||
metadataBytes, err := os.ReadFile(metadataPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
var readMetadata hydratorMetadataFile
|
||||
err = json.Unmarshal(metadataBytes, &readMetadata)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, repoUrl, readMetadata.RepoURL)
|
||||
|
||||
// Check if each path contains a README.md file and contains the repo URL
|
||||
readmePath := path.Join(fullHydratePath, "README.md")
|
||||
readmeBytes, err := os.ReadFile(readmePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(readmeBytes), repoUrl)
|
||||
|
||||
// Check if each path contains a manifest.yaml file and contains the word Pod
|
||||
manifestPath := path.Join(fullHydratePath, "manifest.yaml")
|
||||
manifestBytes, err := os.ReadFile(manifestPath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(manifestBytes), "kind")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMetadata(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
metadata := hydratorMetadataFile{
|
||||
RepoURL: "https://github.com/example/repo",
|
||||
DrySHA: "abc123",
|
||||
}
|
||||
|
||||
err := writeMetadata(dir, metadata)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadataPath := path.Join(dir, "hydrator.metadata")
|
||||
metadataBytes, err := os.ReadFile(metadataPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
var readMetadata hydratorMetadataFile
|
||||
err = json.Unmarshal(metadataBytes, &readMetadata)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, metadata, readMetadata)
|
||||
}
|
||||
|
||||
func TestWriteReadme(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
metadata := hydratorMetadataFile{
|
||||
RepoURL: "https://github.com/example/repo",
|
||||
DrySHA: "abc123",
|
||||
}
|
||||
|
||||
err := writeReadme(dir, metadata)
|
||||
require.NoError(t, err)
|
||||
|
||||
readmePath := path.Join(dir, "README.md")
|
||||
readmeBytes, err := os.ReadFile(readmePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(readmeBytes), metadata.RepoURL)
|
||||
}
|
||||
|
||||
func TestWriteManifests(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
manifests := []*apiclient.HydratedManifestDetails{
|
||||
{ManifestJSON: `{"kind":"Pod","apiVersion":"v1"}`},
|
||||
}
|
||||
|
||||
err := writeManifests(dir, manifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
manifestPath := path.Join(dir, "manifest.yaml")
|
||||
manifestBytes, err := os.ReadFile(manifestPath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(manifestBytes), "kind")
|
||||
}
|
||||
59
commitserver/commit/mocks/RepoClientFactory.go
generated
Normal file
59
commitserver/commit/mocks/RepoClientFactory.go
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
git "github.com/argoproj/argo-cd/v2/util/git"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
// RepoClientFactory is an autogenerated mock type for the RepoClientFactory type
|
||||
type RepoClientFactory struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// NewClient provides a mock function with given fields: repo, rootPath
|
||||
func (_m *RepoClientFactory) NewClient(repo *v1alpha1.Repository, rootPath string) (git.Client, error) {
|
||||
ret := _m.Called(repo, rootPath)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for NewClient")
|
||||
}
|
||||
|
||||
var r0 git.Client
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*v1alpha1.Repository, string) (git.Client, error)); ok {
|
||||
return rf(repo, rootPath)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*v1alpha1.Repository, string) git.Client); ok {
|
||||
r0 = rf(repo, rootPath)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(git.Client)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*v1alpha1.Repository, string) error); ok {
|
||||
r1 = rf(repo, rootPath)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewRepoClientFactory creates a new instance of RepoClientFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewRepoClientFactory(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *RepoClientFactory {
|
||||
mock := &RepoClientFactory{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
32
commitserver/commit/repo_client_factory.go
Normal file
32
commitserver/commit/repo_client_factory.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
)
|
||||
|
||||
// RepoClientFactory is a factory for creating git clients for a repository.
|
||||
type RepoClientFactory interface {
|
||||
NewClient(repo *v1alpha1.Repository, rootPath string) (git.Client, error)
|
||||
}
|
||||
|
||||
type repoClientFactory struct {
|
||||
gitCredsStore git.CredsStore
|
||||
metricsServer *metrics.Server
|
||||
}
|
||||
|
||||
// NewRepoClientFactory returns a new instance of the repo client factory.
|
||||
func NewRepoClientFactory(gitCredsStore git.CredsStore, metricsServer *metrics.Server) RepoClientFactory {
|
||||
return &repoClientFactory{
|
||||
gitCredsStore: gitCredsStore,
|
||||
metricsServer: metricsServer,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient creates a new git client for the repository.
|
||||
func (r *repoClientFactory) NewClient(repo *v1alpha1.Repository, rootPath string) (git.Client, error) {
|
||||
gitCreds := repo.GetGitCreds(r.gitCredsStore)
|
||||
opts := git.WithEventHandlers(metrics.NewGitClientEventHandlers(r.metricsServer))
|
||||
return git.NewClientExt(repo.Repo, rootPath, gitCreds, repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy, repo.NoProxy, opts)
|
||||
}
|
||||
34
commitserver/metrics/githandlers.go
Normal file
34
commitserver/metrics/githandlers.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
)
|
||||
|
||||
// NewGitClientEventHandlers creates event handlers that update Git related metrics
|
||||
func NewGitClientEventHandlers(metricsServer *Server) git.EventHandlers {
|
||||
return git.EventHandlers{
|
||||
OnFetch: func(repo string) func() {
|
||||
startTime := time.Now()
|
||||
metricsServer.IncGitRequest(repo, GitRequestTypeFetch)
|
||||
return func() {
|
||||
metricsServer.ObserveGitRequestDuration(repo, GitRequestTypeFetch, time.Since(startTime))
|
||||
}
|
||||
},
|
||||
OnLsRemote: func(repo string) func() {
|
||||
startTime := time.Now()
|
||||
metricsServer.IncGitRequest(repo, GitRequestTypeLsRemote)
|
||||
return func() {
|
||||
metricsServer.ObserveGitRequestDuration(repo, GitRequestTypeLsRemote, time.Since(startTime))
|
||||
}
|
||||
},
|
||||
OnPush: func(repo string) func() {
|
||||
startTime := time.Now()
|
||||
metricsServer.IncGitRequest(repo, GitRequestTypePush)
|
||||
return func() {
|
||||
metricsServer.ObserveGitRequestDuration(repo, GitRequestTypePush, time.Since(startTime))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
157
commitserver/metrics/metrics.go
Normal file
157
commitserver/metrics/metrics.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// Server is a prometheus server which collects application metrics.
|
||||
type Server struct {
|
||||
handler http.Handler
|
||||
commitPendingRequestsGauge *prometheus.GaugeVec
|
||||
gitRequestCounter *prometheus.CounterVec
|
||||
gitRequestHistogram *prometheus.HistogramVec
|
||||
commitRequestHistogram *prometheus.HistogramVec
|
||||
userInfoRequestHistogram *prometheus.HistogramVec
|
||||
commitRequestCounter *prometheus.CounterVec
|
||||
}
|
||||
|
||||
// GitRequestType is the type of git request
|
||||
type GitRequestType string
|
||||
|
||||
const (
|
||||
// GitRequestTypeLsRemote is a request to list remote refs
|
||||
GitRequestTypeLsRemote = "ls-remote"
|
||||
// GitRequestTypeFetch is a request to fetch from remote
|
||||
GitRequestTypeFetch = "fetch"
|
||||
// GitRequestTypePush is a request to push to remote
|
||||
GitRequestTypePush = "push"
|
||||
)
|
||||
|
||||
// CommitResponseType is the type of response for a commit request
|
||||
type CommitResponseType string
|
||||
|
||||
const (
|
||||
// CommitResponseTypeSuccess is a successful commit request
|
||||
CommitResponseTypeSuccess CommitResponseType = "success"
|
||||
// CommitResponseTypeFailure is a failed commit request
|
||||
CommitResponseTypeFailure CommitResponseType = "failure"
|
||||
)
|
||||
|
||||
// NewMetricsServer returns a new prometheus server which collects application metrics.
|
||||
func NewMetricsServer() *Server {
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
|
||||
registry.MustRegister(collectors.NewGoCollector())
|
||||
|
||||
commitPendingRequestsGauge := prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "argocd_commitserver_commit_pending_request_total",
|
||||
Help: "Number of pending commit requests",
|
||||
},
|
||||
[]string{"repo"},
|
||||
)
|
||||
registry.MustRegister(commitPendingRequestsGauge)
|
||||
|
||||
gitRequestCounter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "argocd_commitserver_git_request_total",
|
||||
Help: "Number of git requests performed by repo server",
|
||||
},
|
||||
[]string{"repo", "request_type"},
|
||||
)
|
||||
registry.MustRegister(gitRequestCounter)
|
||||
|
||||
gitRequestHistogram := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "argocd_commitserver_git_request_duration_seconds",
|
||||
Help: "Git requests duration seconds.",
|
||||
Buckets: []float64{0.1, 0.25, .5, 1, 2, 4, 10, 20},
|
||||
},
|
||||
[]string{"repo", "request_type"},
|
||||
)
|
||||
registry.MustRegister(gitRequestHistogram)
|
||||
|
||||
commitRequestHistogram := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "argocd_commitserver_commit_request_duration_seconds",
|
||||
Help: "Commit request duration seconds.",
|
||||
Buckets: []float64{0.1, 0.25, .5, 1, 2, 4, 10, 20},
|
||||
},
|
||||
[]string{"repo", "response_type"},
|
||||
)
|
||||
registry.MustRegister(commitRequestHistogram)
|
||||
|
||||
userInfoRequestHistogram := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "argocd_commitserver_userinfo_request_duration_seconds",
|
||||
Help: "Userinfo request duration seconds.",
|
||||
Buckets: []float64{0.1, 0.25, .5, 1, 2, 4, 10, 20},
|
||||
},
|
||||
[]string{"repo", "credential_type"},
|
||||
)
|
||||
registry.MustRegister(userInfoRequestHistogram)
|
||||
|
||||
commitRequestCounter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "argocd_commitserver_commit_request_total",
|
||||
Help: "Number of commit requests performed handled",
|
||||
},
|
||||
[]string{"repo", "response_type"},
|
||||
)
|
||||
registry.MustRegister(commitRequestCounter)
|
||||
|
||||
return &Server{
|
||||
handler: promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
|
||||
commitPendingRequestsGauge: commitPendingRequestsGauge,
|
||||
gitRequestCounter: gitRequestCounter,
|
||||
gitRequestHistogram: gitRequestHistogram,
|
||||
commitRequestHistogram: commitRequestHistogram,
|
||||
userInfoRequestHistogram: userInfoRequestHistogram,
|
||||
commitRequestCounter: commitRequestCounter,
|
||||
}
|
||||
}
|
||||
|
||||
// GetHandler returns the http.Handler for the prometheus server
|
||||
func (m *Server) GetHandler() http.Handler {
|
||||
return m.handler
|
||||
}
|
||||
|
||||
// IncPendingCommitRequest increments the pending commit requests gauge
|
||||
func (m *Server) IncPendingCommitRequest(repo string) {
|
||||
m.commitPendingRequestsGauge.WithLabelValues(repo).Inc()
|
||||
}
|
||||
|
||||
// DecPendingCommitRequest decrements the pending commit requests gauge
|
||||
func (m *Server) DecPendingCommitRequest(repo string) {
|
||||
m.commitPendingRequestsGauge.WithLabelValues(repo).Dec()
|
||||
}
|
||||
|
||||
// IncGitRequest increments the git requests counter
|
||||
func (m *Server) IncGitRequest(repo string, requestType GitRequestType) {
|
||||
m.gitRequestCounter.WithLabelValues(repo, string(requestType)).Inc()
|
||||
}
|
||||
|
||||
// ObserveGitRequestDuration observes the duration of a git request
|
||||
func (m *Server) ObserveGitRequestDuration(repo string, requestType GitRequestType, duration time.Duration) {
|
||||
m.gitRequestHistogram.WithLabelValues(repo, string(requestType)).Observe(duration.Seconds())
|
||||
}
|
||||
|
||||
// ObserveCommitRequestDuration observes the duration of a commit request
|
||||
func (m *Server) ObserveCommitRequestDuration(repo string, rt CommitResponseType, duration time.Duration) {
|
||||
m.commitRequestHistogram.WithLabelValues(repo, string(rt)).Observe(duration.Seconds())
|
||||
}
|
||||
|
||||
// ObserveUserInfoRequestDuration observes the duration of a userinfo request
|
||||
func (m *Server) ObserveUserInfoRequestDuration(repo string, credentialType string, duration time.Duration) {
|
||||
m.userInfoRequestHistogram.WithLabelValues(repo, credentialType).Observe(duration.Seconds())
|
||||
}
|
||||
|
||||
// IncCommitRequest increments the commit request counter
|
||||
func (m *Server) IncCommitRequest(repo string, rt CommitResponseType) {
|
||||
m.commitRequestCounter.WithLabelValues(repo, string(rt)).Inc()
|
||||
}
|
||||
38
commitserver/server.go
Normal file
38
commitserver/server.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package commitserver
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/commit"
|
||||
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
|
||||
versionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/version"
|
||||
"github.com/argoproj/argo-cd/v2/server/version"
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
)
|
||||
|
||||
// ArgoCDCommitServer is the server that handles commit requests.
|
||||
type ArgoCDCommitServer struct {
|
||||
commitService *commit.Service
|
||||
}
|
||||
|
||||
// NewServer returns a new instance of the commit server.
|
||||
func NewServer(gitCredsStore git.CredsStore, metricsServer *metrics.Server) *ArgoCDCommitServer {
|
||||
return &ArgoCDCommitServer{commitService: commit.NewService(gitCredsStore, metricsServer)}
|
||||
}
|
||||
|
||||
// CreateGRPC creates a new gRPC server.
|
||||
func (a *ArgoCDCommitServer) CreateGRPC() *grpc.Server {
|
||||
server := grpc.NewServer()
|
||||
versionpkg.RegisterVersionServiceServer(server, version.NewServer(nil, func() (bool, error) {
|
||||
return true, nil
|
||||
}))
|
||||
apiclient.RegisterCommitServiceServer(server, a.commitService)
|
||||
|
||||
healthService := health.NewServer()
|
||||
grpc_health_v1.RegisterHealthServer(server, healthService)
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -62,15 +62,19 @@ const (
|
||||
DefaultPortArgoCDMetrics = 8082
|
||||
DefaultPortArgoCDAPIServerMetrics = 8083
|
||||
DefaultPortRepoServerMetrics = 8084
|
||||
DefaultPortCommitServer = 8086
|
||||
DefaultPortCommitServerMetrics = 8087
|
||||
)
|
||||
|
||||
// DefaultAddressAPIServer for ArgoCD components
|
||||
const (
|
||||
DefaultAddressAdminDashboard = "localhost"
|
||||
DefaultAddressAPIServer = "0.0.0.0"
|
||||
DefaultAddressAPIServerMetrics = "0.0.0.0"
|
||||
DefaultAddressRepoServer = "0.0.0.0"
|
||||
DefaultAddressRepoServerMetrics = "0.0.0.0"
|
||||
DefaultAddressAdminDashboard = "localhost"
|
||||
DefaultAddressAPIServer = "0.0.0.0"
|
||||
DefaultAddressAPIServerMetrics = "0.0.0.0"
|
||||
DefaultAddressRepoServer = "0.0.0.0"
|
||||
DefaultAddressRepoServerMetrics = "0.0.0.0"
|
||||
DefaultAddressCommitServer = "0.0.0.0"
|
||||
DefaultAddressCommitServerMetrics = "0.0.0.0"
|
||||
)
|
||||
|
||||
// Default paths on the pod's file system
|
||||
|
||||
@@ -129,6 +129,20 @@ Scraped at the `argocd-repo-server:8084/metrics` endpoint.
|
||||
| `argocd_redis_request_total` | counter | Number of Kubernetes requests executed during application reconciliation. |
|
||||
| `argocd_repo_pending_request_total` | gauge | Number of pending requests requiring repository lock |
|
||||
|
||||
## Commit Server Metrics
|
||||
|
||||
Metrics about the Commit Server.
|
||||
Scraped at the `argocd-commit-server:8087/metrics` endpoint.
|
||||
|
||||
| Metric | Type | Description |
|
||||
|---------------------------------------------------------|:---------:|------------------------------------------------------|
|
||||
| `argocd_commitserver_commit_pending_request_total` | guage | Number of pending commit requests. |
|
||||
| `argocd_commitserver_git_request_duration_seconds` | histogram | Git requests duration seconds. |
|
||||
| `argocd_commitserver_git_request_total` | counter | Number of git requests performed by commit server |
|
||||
| `argocd_commitserver_commit_request_duration_seconds` | histogram | Commit requests duration seconds. |
|
||||
| `argocd_commitserver_userinfo_request_duration_seconds` | histogram | Userinfo requests duration seconds. |
|
||||
| `argocd_commitserver_commit_request_total` | counter | Number of commit requests performed by commit server |
|
||||
|
||||
## Prometheus Operator
|
||||
|
||||
If using Prometheus Operator, the following ServiceMonitor example manifests can be used.
|
||||
|
||||
3
go.mod
3
go.mod
@@ -43,7 +43,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/btree v1.1.3
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-github/v63 v63.0.0
|
||||
github.com/google/go-github/v66 v66.0.0
|
||||
github.com/google/go-jsonnet v0.20.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -139,7 +139,6 @@ require (
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-github/v66 v66.0.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -437,8 +437,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
|
||||
github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
|
||||
github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE=
|
||||
github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA=
|
||||
github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M=
|
||||
github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4=
|
||||
github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
|
||||
|
||||
@@ -95,7 +95,7 @@ MOD_ROOT=${GOPATH}/pkg/mod
|
||||
grpc_gateway_version=$(go list -m github.com/grpc-ecosystem/grpc-gateway | awk '{print $NF}' | head -1)
|
||||
GOOGLE_PROTO_API_PATH=${MOD_ROOT}/github.com/grpc-ecosystem/grpc-gateway@${grpc_gateway_version}/third_party/googleapis
|
||||
GOGO_PROTOBUF_PATH=${PROJECT_ROOT}/vendor/github.com/gogo/protobuf
|
||||
PROTO_FILES=$(find "$PROJECT_ROOT" \( -name "*.proto" -and -path '*/server/*' -or -path '*/reposerver/*' -and -name "*.proto" -or -path '*/cmpserver/*' -and -name "*.proto" \) | sort)
|
||||
PROTO_FILES=$(find "$PROJECT_ROOT" \( -name "*.proto" -and -path '*/server/*' -or -path '*/reposerver/*' -and -name "*.proto" -or -path '*/cmpserver/*' -and -name "*.proto" -or -path '*/commitserver/*' -and -name "*.proto" -or -path '*/util/askpass/*' -and -name "*.proto" \) | sort)
|
||||
for i in ${PROTO_FILES}; do
|
||||
protoc \
|
||||
-I"${PROJECT_ROOT}" \
|
||||
@@ -110,6 +110,9 @@ for i in ${PROTO_FILES}; do
|
||||
"$i"
|
||||
done
|
||||
|
||||
# This file is generated but should not be checked in.
|
||||
rm util/askpass/askpass.swagger.json
|
||||
|
||||
[ -L "${GOPATH_PROJECT_ROOT}" ] && rm -rf "${GOPATH_PROJECT_ROOT}"
|
||||
[ -L ./v2 ] && rm -rf v2
|
||||
|
||||
@@ -162,3 +165,4 @@ clean_swagger server
|
||||
clean_swagger reposerver
|
||||
clean_swagger controller
|
||||
clean_swagger cmpserver
|
||||
clean_swagger commitserver
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
app.kubernetes.io/part-of: argocd
|
||||
app.kubernetes.io/component: commit-server
|
||||
name: argocd-commit-server
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
spec:
|
||||
serviceAccountName: argocd-commit-server
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: argocd-commit-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- /usr/local/bin/argocd-commit-server
|
||||
env:
|
||||
- name: ARGOCD_COMMIT_SERVER_LISTEN_ADDRESS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: commitserver.listen.address
|
||||
optional: true
|
||||
- name: ARGOCD_COMMIT_SERVER_METRICS_LISTEN_ADDRESS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: commitserver.metrics.listen.address
|
||||
optional: true
|
||||
- name: ARGOCD_COMMIT_SERVER_LOGFORMAT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: commitserver.log.format
|
||||
optional: true
|
||||
- name: ARGOCD_COMMIT_SERVER_LOGLEVEL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: commitserver.log.level
|
||||
optional: true
|
||||
ports:
|
||||
- containerPort: 8086
|
||||
- containerPort: 8087
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz?full=true
|
||||
port: 8087
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
failureThreshold: 3
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8087
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: ssh-known-hosts
|
||||
mountPath: /app/config/ssh
|
||||
- name: tls-certs
|
||||
mountPath: /app/config/tls
|
||||
- name: gpg-keys
|
||||
mountPath: /app/config/gpg/source
|
||||
- name: gpg-keyring
|
||||
mountPath: /app/config/gpg/keys
|
||||
initContainers:
|
||||
- command:
|
||||
- /bin/cp
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
name: copyutil
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
volumes:
|
||||
- name: ssh-known-hosts
|
||||
configMap:
|
||||
name: argocd-ssh-known-hosts-cm
|
||||
- name: tls-certs
|
||||
configMap:
|
||||
name: argocd-tls-certs-cm
|
||||
- name: gpg-keys
|
||||
configMap:
|
||||
name: argocd-gpg-keys-cm
|
||||
- name: gpg-keyring
|
||||
emptyDir: {}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: helm-working-dir
|
||||
emptyDir: {}
|
||||
- name: argocd-commit-server-tls
|
||||
secret:
|
||||
secretName: argocd-commit-server-tls
|
||||
optional: true
|
||||
items:
|
||||
- key: tls.crt
|
||||
path: tls.crt
|
||||
- key: tls.key
|
||||
path: tls.key
|
||||
- key: ca.crt
|
||||
path: ca.crt
|
||||
- emptyDir: {}
|
||||
name: var-files
|
||||
- emptyDir: {}
|
||||
name: plugins
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
topologyKey: kubernetes.io/hostname
|
||||
- weight: 5
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/part-of: argocd
|
||||
topologyKey: kubernetes.io/hostname
|
||||
@@ -0,0 +1,22 @@
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: argocd-commit-server-network-policy
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: argocd-application-controller
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8086
|
||||
- from:
|
||||
- namespaceSelector: { }
|
||||
ports:
|
||||
- port: 8087
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
app.kubernetes.io/part-of: argocd
|
||||
app.kubernetes.io/component: commit-server
|
||||
name: argocd-commit-server
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
app.kubernetes.io/part-of: argocd
|
||||
app.kubernetes.io/component: commit-server
|
||||
name: argocd-commit-server
|
||||
spec:
|
||||
ports:
|
||||
- name: server
|
||||
protocol: TCP
|
||||
port: 8086
|
||||
targetPort: 8086
|
||||
- name: metrics
|
||||
protocol: TCP
|
||||
port: 8087
|
||||
targetPort: 8087
|
||||
selector:
|
||||
app.kubernetes.io/name: argocd-commit-server
|
||||
8
manifests/base/commit-server/kustomization.yaml
Normal file
8
manifests/base/commit-server/kustomization.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- argocd-commit-server-sa.yaml
|
||||
- argocd-commit-server-deployment.yaml
|
||||
- argocd-commit-server-service.yaml
|
||||
- argocd-commit-server-network-policy.yaml
|
||||
1093
manifests/core-install.yaml
generated
1093
manifests/core-install.yaml
generated
File diff suppressed because it is too large
Load Diff
229
manifests/crds/application-crd.yaml
generated
229
manifests/crds/application-crd.yaml
generated
@@ -1403,6 +1403,64 @@ spec:
|
||||
required:
|
||||
- repoURL
|
||||
type: object
|
||||
sourceHydrator:
|
||||
description: SourceHydrator provides a way to push hydrated manifests
|
||||
back to git before syncing them to the cluster.
|
||||
properties:
|
||||
drySource:
|
||||
description: DrySource specifies where the dry "don't repeat yourself"
|
||||
manifest source lives.
|
||||
properties:
|
||||
path:
|
||||
description: Path is a directory path within the Git repository
|
||||
where the manifests are located
|
||||
type: string
|
||||
repoURL:
|
||||
description: RepoURL is the URL to the git repository that
|
||||
contains the application manifests
|
||||
type: string
|
||||
targetRevision:
|
||||
description: TargetRevision defines the revision of the source
|
||||
to hydrate
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- repoURL
|
||||
- targetRevision
|
||||
type: object
|
||||
hydrateTo:
|
||||
description: |-
|
||||
HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then
|
||||
have to move manifests to the SyncSource, e.g. by pull request.
|
||||
properties:
|
||||
targetBranch:
|
||||
description: TargetBranch is the branch to which hydrated
|
||||
manifests should be committed
|
||||
type: string
|
||||
required:
|
||||
- targetBranch
|
||||
type: object
|
||||
syncSource:
|
||||
description: SyncSource specifies where to sync hydrated manifests
|
||||
from.
|
||||
properties:
|
||||
path:
|
||||
description: |-
|
||||
Path is a directory path within the git repository where hydrated manifests should be committed to and synced
|
||||
from. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.
|
||||
type: string
|
||||
targetBranch:
|
||||
description: TargetBranch is the branch to which hydrated
|
||||
manifests should be committed
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- targetBranch
|
||||
type: object
|
||||
required:
|
||||
- drySource
|
||||
- syncSource
|
||||
type: object
|
||||
sources:
|
||||
description: Sources is a reference to the location of the application's
|
||||
manifests or chart
|
||||
@@ -4616,6 +4674,177 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
sourceHydrator:
|
||||
description: SourceHydrator stores information about the current state
|
||||
of source hydration
|
||||
properties:
|
||||
currentOperation:
|
||||
description: CurrentOperation holds the status of the hydrate
|
||||
operation
|
||||
properties:
|
||||
drySHA:
|
||||
description: DrySHA holds the resolved revision (sha) of the
|
||||
dry source as of the most recent reconciliation
|
||||
type: string
|
||||
finishedAt:
|
||||
description: FinishedAt indicates when the hydrate operation
|
||||
finished
|
||||
format: date-time
|
||||
type: string
|
||||
hydratedSHA:
|
||||
description: HydratedSHA holds the resolved revision (sha)
|
||||
of the hydrated source as of the most recent reconciliation
|
||||
type: string
|
||||
message:
|
||||
description: Message contains a message describing the current
|
||||
status of the hydrate operation
|
||||
type: string
|
||||
phase:
|
||||
description: Phase indicates the status of the hydrate operation
|
||||
enum:
|
||||
- Hydrating
|
||||
- Failed
|
||||
- Hydrated
|
||||
type: string
|
||||
sourceHydrator:
|
||||
description: SourceHydrator holds the hydrator config used
|
||||
for the hydrate operation
|
||||
properties:
|
||||
drySource:
|
||||
description: DrySource specifies where the dry "don't
|
||||
repeat yourself" manifest source lives.
|
||||
properties:
|
||||
path:
|
||||
description: Path is a directory path within the Git
|
||||
repository where the manifests are located
|
||||
type: string
|
||||
repoURL:
|
||||
description: RepoURL is the URL to the git repository
|
||||
that contains the application manifests
|
||||
type: string
|
||||
targetRevision:
|
||||
description: TargetRevision defines the revision of
|
||||
the source to hydrate
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- repoURL
|
||||
- targetRevision
|
||||
type: object
|
||||
hydrateTo:
|
||||
description: |-
|
||||
HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then
|
||||
have to move manifests to the SyncSource, e.g. by pull request.
|
||||
properties:
|
||||
targetBranch:
|
||||
description: TargetBranch is the branch to which hydrated
|
||||
manifests should be committed
|
||||
type: string
|
||||
required:
|
||||
- targetBranch
|
||||
type: object
|
||||
syncSource:
|
||||
description: SyncSource specifies where to sync hydrated
|
||||
manifests from.
|
||||
properties:
|
||||
path:
|
||||
description: |-
|
||||
Path is a directory path within the git repository where hydrated manifests should be committed to and synced
|
||||
from. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.
|
||||
type: string
|
||||
targetBranch:
|
||||
description: TargetBranch is the branch to which hydrated
|
||||
manifests should be committed
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- targetBranch
|
||||
type: object
|
||||
required:
|
||||
- drySource
|
||||
- syncSource
|
||||
type: object
|
||||
startedAt:
|
||||
description: StartedAt indicates when the hydrate operation
|
||||
started
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- message
|
||||
- phase
|
||||
type: object
|
||||
lastSuccessfulOperation:
|
||||
description: LastSuccessfulOperation holds info about the most
|
||||
recent successful hydration
|
||||
properties:
|
||||
drySHA:
|
||||
description: DrySHA holds the resolved revision (sha) of the
|
||||
dry source as of the most recent reconciliation
|
||||
type: string
|
||||
hydratedSHA:
|
||||
description: HydratedSHA holds the resolved revision (sha)
|
||||
of the hydrated source as of the most recent reconciliation
|
||||
type: string
|
||||
sourceHydrator:
|
||||
description: SourceHydrator holds the hydrator config used
|
||||
for the hydrate operation
|
||||
properties:
|
||||
drySource:
|
||||
description: DrySource specifies where the dry "don't
|
||||
repeat yourself" manifest source lives.
|
||||
properties:
|
||||
path:
|
||||
description: Path is a directory path within the Git
|
||||
repository where the manifests are located
|
||||
type: string
|
||||
repoURL:
|
||||
description: RepoURL is the URL to the git repository
|
||||
that contains the application manifests
|
||||
type: string
|
||||
targetRevision:
|
||||
description: TargetRevision defines the revision of
|
||||
the source to hydrate
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- repoURL
|
||||
- targetRevision
|
||||
type: object
|
||||
hydrateTo:
|
||||
description: |-
|
||||
HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then
|
||||
have to move manifests to the SyncSource, e.g. by pull request.
|
||||
properties:
|
||||
targetBranch:
|
||||
description: TargetBranch is the branch to which hydrated
|
||||
manifests should be committed
|
||||
type: string
|
||||
required:
|
||||
- targetBranch
|
||||
type: object
|
||||
syncSource:
|
||||
description: SyncSource specifies where to sync hydrated
|
||||
manifests from.
|
||||
properties:
|
||||
path:
|
||||
description: |-
|
||||
Path is a directory path within the git repository where hydrated manifests should be committed to and synced
|
||||
from. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.
|
||||
type: string
|
||||
targetBranch:
|
||||
description: TargetBranch is the branch to which hydrated
|
||||
manifests should be committed
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- targetBranch
|
||||
type: object
|
||||
required:
|
||||
- drySource
|
||||
- syncSource
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
sourceType:
|
||||
description: SourceType specifies the type of this application
|
||||
type: string
|
||||
|
||||
864
manifests/crds/applicationset-crd.yaml
generated
864
manifests/crds/applicationset-crd.yaml
generated
File diff suppressed because it is too large
Load Diff
1093
manifests/ha/install.yaml
generated
1093
manifests/ha/install.yaml
generated
File diff suppressed because it is too large
Load Diff
1093
manifests/install.yaml
generated
1093
manifests/install.yaml
generated
File diff suppressed because it is too large
Load Diff
3522
pkg/apis/application/v1alpha1/generated.pb.go
generated
3522
pkg/apis/application/v1alpha1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -644,6 +644,9 @@ message ApplicationSpec {
|
||||
|
||||
// Sources is a reference to the location of the application's manifests or chart
|
||||
repeated ApplicationSource sources = 8;
|
||||
|
||||
// SourceHydrator provides a way to push hydrated manifests back to git before syncing them to the cluster.
|
||||
optional SourceHydrator sourceHydrator = 9;
|
||||
}
|
||||
|
||||
// ApplicationStatus contains status information for the application
|
||||
@@ -687,6 +690,9 @@ message ApplicationStatus {
|
||||
|
||||
// ControllerNamespace indicates the namespace in which the application controller is located
|
||||
optional string controllerNamespace = 13;
|
||||
|
||||
// SourceHydrator stores information about the current state of source hydration
|
||||
optional SourceHydratorStatus sourceHydrator = 14;
|
||||
}
|
||||
|
||||
// ApplicationSummary contains information about URLs and container images used by an application
|
||||
@@ -957,6 +963,18 @@ message ConnectionState {
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time attemptedAt = 3;
|
||||
}
|
||||
|
||||
// DrySource specifies a location for dry "don't repeat yourself" manifest source information.
|
||||
message DrySource {
|
||||
// RepoURL is the URL to the git repository that contains the application manifests
|
||||
optional string repoURL = 1;
|
||||
|
||||
// TargetRevision defines the revision of the source to hydrate
|
||||
optional string targetRevision = 2;
|
||||
|
||||
// Path is a directory path within the Git repository where the manifests are located
|
||||
optional string path = 3;
|
||||
}
|
||||
|
||||
// DuckType defines a generator to match against clusters registered with ArgoCD.
|
||||
message DuckTypeGenerator {
|
||||
// ConfigMapRef is a ConfigMap with the duck type definitions needed to retrieve the data
|
||||
@@ -1125,6 +1143,37 @@ message HostResourceInfo {
|
||||
optional int64 capacity = 4;
|
||||
}
|
||||
|
||||
// HydrateOperation contains information about the most recent hydrate operation
|
||||
message HydrateOperation {
|
||||
// StartedAt indicates when the hydrate operation started
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time startedAt = 1;
|
||||
|
||||
// FinishedAt indicates when the hydrate operation finished
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time finishedAt = 2;
|
||||
|
||||
// Phase indicates the status of the hydrate operation
|
||||
optional string phase = 3;
|
||||
|
||||
// Message contains a message describing the current status of the hydrate operation
|
||||
optional string message = 4;
|
||||
|
||||
// DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation
|
||||
optional string drySHA = 5;
|
||||
|
||||
// HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation
|
||||
optional string hydratedSHA = 6;
|
||||
|
||||
// SourceHydrator holds the hydrator config used for the hydrate operation
|
||||
optional SourceHydrator sourceHydrator = 7;
|
||||
}
|
||||
|
||||
// HydrateTo specifies a location to which hydrated manifests should be pushed as a "staging area" before being moved to
|
||||
// the SyncSource. The RepoURL and Path are assumed based on the associated SyncSource config in the SourceHydrator.
|
||||
message HydrateTo {
|
||||
// TargetBranch is the branch to which hydrated manifests should be committed
|
||||
optional string targetBranch = 1;
|
||||
}
|
||||
|
||||
message Info {
|
||||
optional string name = 1;
|
||||
|
||||
@@ -2244,6 +2293,41 @@ message SignatureKey {
|
||||
optional string keyID = 1;
|
||||
}
|
||||
|
||||
// SourceHydrator specifies a dry "don't repeat yourself" source for manifests, a sync source from which to sync
|
||||
// hydrated manifests, and an optional hydrateTo location to act as a "staging" aread for hydrated manifests.
|
||||
message SourceHydrator {
|
||||
// DrySource specifies where the dry "don't repeat yourself" manifest source lives.
|
||||
optional DrySource drySource = 1;
|
||||
|
||||
// SyncSource specifies where to sync hydrated manifests from.
|
||||
optional SyncSource syncSource = 2;
|
||||
|
||||
// HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then
|
||||
// have to move manifests to the SyncSource, e.g. by pull request.
|
||||
optional HydrateTo hydrateTo = 3;
|
||||
}
|
||||
|
||||
// SourceHydratorStatus contains information about the current state of source hydration
|
||||
message SourceHydratorStatus {
|
||||
// LastSuccessfulOperation holds info about the most recent successful hydration
|
||||
optional SuccessfulHydrateOperation lastSuccessfulOperation = 1;
|
||||
|
||||
// CurrentOperation holds the status of the hydrate operation
|
||||
optional HydrateOperation currentOperation = 2;
|
||||
}
|
||||
|
||||
// SuccessfulHydrateOperation contains information about the most recent successful hydrate operation
|
||||
message SuccessfulHydrateOperation {
|
||||
// DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation
|
||||
optional string drySHA = 5;
|
||||
|
||||
// HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation
|
||||
optional string hydratedSHA = 6;
|
||||
|
||||
// SourceHydrator holds the hydrator config used for the hydrate operation
|
||||
optional SourceHydrator sourceHydrator = 7;
|
||||
}
|
||||
|
||||
// SyncOperation contains details about a sync operation.
|
||||
message SyncOperation {
|
||||
// Revision is the revision (Git) or chart version (Helm) which to sync the application to
|
||||
@@ -2343,6 +2427,17 @@ message SyncPolicyAutomated {
|
||||
optional bool allowEmpty = 3;
|
||||
}
|
||||
|
||||
// SyncSource specifies a location from which hydrated manifests may be synced. RepoURL is assumed based on the
|
||||
// associated DrySource config in the SourceHydrator.
|
||||
message SyncSource {
|
||||
// TargetBranch is the branch to which hydrated manifests should be committed
|
||||
optional string targetBranch = 1;
|
||||
|
||||
// Path is a directory path within the git repository where hydrated manifests should be committed to and synced
|
||||
// from. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.
|
||||
optional string path = 2;
|
||||
}
|
||||
|
||||
// SyncStatus contains information about the currently observed live and desired states of an application
|
||||
message SyncStatus {
|
||||
// Status is the sync state of the comparison
|
||||
|
||||
@@ -72,6 +72,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ConfigManagementPlugin": schema_pkg_apis_application_v1alpha1_ConfigManagementPlugin(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ConfigMapKeyRef": schema_pkg_apis_application_v1alpha1_ConfigMapKeyRef(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ConnectionState": schema_pkg_apis_application_v1alpha1_ConnectionState(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.DrySource": schema_pkg_apis_application_v1alpha1_DrySource(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.DuckTypeGenerator": schema_pkg_apis_application_v1alpha1_DuckTypeGenerator(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.EnvEntry": schema_pkg_apis_application_v1alpha1_EnvEntry(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ErrApplicationNotAllowedToUseProject": schema_pkg_apis_application_v1alpha1_ErrApplicationNotAllowedToUseProject(ref),
|
||||
@@ -87,6 +88,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HelmParameter": schema_pkg_apis_application_v1alpha1_HelmParameter(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HostInfo": schema_pkg_apis_application_v1alpha1_HostInfo(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HostResourceInfo": schema_pkg_apis_application_v1alpha1_HostResourceInfo(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HydrateOperation": schema_pkg_apis_application_v1alpha1_HydrateOperation(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HydrateTo": schema_pkg_apis_application_v1alpha1_HydrateTo(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.Info": schema_pkg_apis_application_v1alpha1_Info(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.InfoItem": schema_pkg_apis_application_v1alpha1_InfoItem(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.JWTToken": schema_pkg_apis_application_v1alpha1_JWTToken(ref),
|
||||
@@ -158,11 +161,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SCMProviderGeneratorGitlab": schema_pkg_apis_application_v1alpha1_SCMProviderGeneratorGitlab(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SecretRef": schema_pkg_apis_application_v1alpha1_SecretRef(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SignatureKey": schema_pkg_apis_application_v1alpha1_SignatureKey(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydrator": schema_pkg_apis_application_v1alpha1_SourceHydrator(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydratorStatus": schema_pkg_apis_application_v1alpha1_SourceHydratorStatus(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SuccessfulHydrateOperation": schema_pkg_apis_application_v1alpha1_SuccessfulHydrateOperation(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncOperation": schema_pkg_apis_application_v1alpha1_SyncOperation(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncOperationResource": schema_pkg_apis_application_v1alpha1_SyncOperationResource(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncOperationResult": schema_pkg_apis_application_v1alpha1_SyncOperationResult(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncPolicy": schema_pkg_apis_application_v1alpha1_SyncPolicy(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncPolicyAutomated": schema_pkg_apis_application_v1alpha1_SyncPolicyAutomated(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncSource": schema_pkg_apis_application_v1alpha1_SyncSource(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncStatus": schema_pkg_apis_application_v1alpha1_SyncStatus(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncStrategy": schema_pkg_apis_application_v1alpha1_SyncStrategy(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncStrategyApply": schema_pkg_apis_application_v1alpha1_SyncStrategyApply(ref),
|
||||
@@ -2317,12 +2324,18 @@ func schema_pkg_apis_application_v1alpha1_ApplicationSpec(ref common.ReferenceCa
|
||||
},
|
||||
},
|
||||
},
|
||||
"sourceHydrator": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SourceHydrator provides a way to push hydrated manifests back to git before syncing them to the cluster.",
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydrator"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"destination", "project"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationDestination", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSource", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.Info", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ResourceIgnoreDifferences", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncPolicy"},
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationDestination", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSource", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.Info", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ResourceIgnoreDifferences", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydrator", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncPolicy"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2450,11 +2463,18 @@ func schema_pkg_apis_application_v1alpha1_ApplicationStatus(ref common.Reference
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sourceHydrator": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SourceHydrator stores information about the current state of source hydration",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydratorStatus"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationCondition", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSummary", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HealthStatus", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OperationState", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ResourceStatus", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.RevisionHistory", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationCondition", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSummary", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HealthStatus", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OperationState", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ResourceStatus", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.RevisionHistory", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydratorStatus", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3346,6 +3366,44 @@ func schema_pkg_apis_application_v1alpha1_ConnectionState(ref common.ReferenceCa
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_DrySource(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DrySource specifies a location for dry \"don't repeat yourself\" manifest source information.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"repoURL": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "RepoURL is the URL to the git repository that contains the application manifests",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"targetRevision": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TargetRevision defines the revision of the source to hydrate",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"path": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Path is a directory path within the Git repository where the manifests are located",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"repoURL", "targetRevision", "path"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_DuckTypeGenerator(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@@ -3966,6 +4024,93 @@ func schema_pkg_apis_application_v1alpha1_HostResourceInfo(ref common.ReferenceC
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_HydrateOperation(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "HydrateOperation contains information about the most recent hydrate operation",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"startedAt": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "StartedAt indicates when the hydrate operation started",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||
},
|
||||
},
|
||||
"finishedAt": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "FinishedAt indicates when the hydrate operation finished",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||
},
|
||||
},
|
||||
"phase": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Phase indicates the status of the hydrate operation",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"message": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Message contains a message describing the current status of the hydrate operation",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"drySHA": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"hydratedSHA": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sourceHydrator": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SourceHydrator holds the hydrator config used for the hydrate operation",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydrator"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"phase", "message"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydrator", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_HydrateTo(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "HydrateTo specifies a location to which hydrated manifests should be pushed as a \"staging area\" before being moved to the SyncSource. The RepoURL and Path are assumed based on the associated SyncSource config in the SourceHydrator.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"targetBranch": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TargetBranch is the branch to which hydrated manifests should be committed",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"targetBranch"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_Info(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@@ -7651,6 +7796,105 @@ func schema_pkg_apis_application_v1alpha1_SignatureKey(ref common.ReferenceCallb
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_SourceHydrator(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SourceHydrator specifies a dry \"don't repeat yourself\" source for manifests, a sync source from which to sync hydrated manifests, and an optional hydrateTo location to act as a \"staging\" aread for hydrated manifests.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"drySource": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DrySource specifies where the dry \"don't repeat yourself\" manifest source lives.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.DrySource"),
|
||||
},
|
||||
},
|
||||
"syncSource": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SyncSource specifies where to sync hydrated manifests from.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncSource"),
|
||||
},
|
||||
},
|
||||
"hydrateTo": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "HydrateTo specifies an optional \"staging\" location to push hydrated manifests to. An external system would then have to move manifests to the SyncSource, e.g. by pull request.",
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HydrateTo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"drySource", "syncSource"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.DrySource", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HydrateTo", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SyncSource"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_SourceHydratorStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SourceHydratorStatus contains information about the current state of source hydration",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"lastSuccessfulOperation": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "LastSuccessfulOperation holds info about the most recent successful hydration",
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SuccessfulHydrateOperation"),
|
||||
},
|
||||
},
|
||||
"currentOperation": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "CurrentOperation holds the status of the hydrate operation",
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HydrateOperation"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.HydrateOperation", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SuccessfulHydrateOperation"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_SuccessfulHydrateOperation(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SuccessfulHydrateOperation contains information about the most recent successful hydrate operation",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"drySHA": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"hydratedSHA": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sourceHydrator": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SourceHydrator holds the hydrator config used for the hydrate operation",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydrator"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.SourceHydrator"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_SyncOperation(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@@ -7973,6 +8217,36 @@ func schema_pkg_apis_application_v1alpha1_SyncPolicyAutomated(ref common.Referen
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_SyncSource(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "SyncSource specifies a location from which hydrated manifests may be synced. RepoURL is assumed based on the associated DrySource config in the SourceHydrator.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"targetBranch": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TargetBranch is the branch to which hydrated manifests should be committed",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"path": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Path is a directory path within the git repository where hydrated manifests should be committed to and synced from. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"targetBranch", "path"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_SyncStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
||||
@@ -86,6 +86,9 @@ type ApplicationSpec struct {
|
||||
|
||||
// Sources is a reference to the location of the application's manifests or chart
|
||||
Sources ApplicationSources `json:"sources,omitempty" protobuf:"bytes,8,opt,name=sources"`
|
||||
|
||||
// SourceHydrator provides a way to push hydrated manifests back to git before syncing them to the cluster.
|
||||
SourceHydrator *SourceHydrator `json:"sourceHydrator,omitempty" protobuf:"bytes,9,opt,name=sourceHydrator"`
|
||||
}
|
||||
|
||||
type IgnoreDifferences []ResourceIgnoreDifferences
|
||||
@@ -351,6 +354,45 @@ const (
|
||||
ApplicationSourceTypePlugin ApplicationSourceType = "Plugin"
|
||||
)
|
||||
|
||||
// SourceHydrator specifies a dry "don't repeat yourself" source for manifests, a sync source from which to sync
|
||||
// hydrated manifests, and an optional hydrateTo location to act as a "staging" aread for hydrated manifests.
|
||||
type SourceHydrator struct {
|
||||
// DrySource specifies where the dry "don't repeat yourself" manifest source lives.
|
||||
DrySource DrySource `json:"drySource" protobuf:"bytes,1,name=drySource"`
|
||||
// SyncSource specifies where to sync hydrated manifests from.
|
||||
SyncSource SyncSource `json:"syncSource" protobuf:"bytes,2,name=syncSource"`
|
||||
// HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then
|
||||
// have to move manifests to the SyncSource, e.g. by pull request.
|
||||
HydrateTo *HydrateTo `json:"hydrateTo,omitempty" protobuf:"bytes,3,opt,name=hydrateTo"`
|
||||
}
|
||||
|
||||
// DrySource specifies a location for dry "don't repeat yourself" manifest source information.
|
||||
type DrySource struct {
|
||||
// RepoURL is the URL to the git repository that contains the application manifests
|
||||
RepoURL string `json:"repoURL" protobuf:"bytes,1,name=repoURL"`
|
||||
// TargetRevision defines the revision of the source to hydrate
|
||||
TargetRevision string `json:"targetRevision" protobuf:"bytes,2,name=targetRevision"`
|
||||
// Path is a directory path within the Git repository where the manifests are located
|
||||
Path string `json:"path" protobuf:"bytes,3,name=path"`
|
||||
}
|
||||
|
||||
// SyncSource specifies a location from which hydrated manifests may be synced. RepoURL is assumed based on the
|
||||
// associated DrySource config in the SourceHydrator.
|
||||
type SyncSource struct {
|
||||
// TargetBranch is the branch to which hydrated manifests should be committed
|
||||
TargetBranch string `json:"targetBranch" protobuf:"bytes,1,name=targetBranch"`
|
||||
// Path is a directory path within the git repository where hydrated manifests should be committed to and synced
|
||||
// from. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.
|
||||
Path string `json:"path" protobuf:"bytes,2,name=path"`
|
||||
}
|
||||
|
||||
// HydrateTo specifies a location to which hydrated manifests should be pushed as a "staging area" before being moved to
|
||||
// the SyncSource. The RepoURL and Path are assumed based on the associated SyncSource config in the SourceHydrator.
|
||||
type HydrateTo struct {
|
||||
// TargetBranch is the branch to which hydrated manifests should be committed
|
||||
TargetBranch string `json:"targetBranch" protobuf:"bytes,1,name=targetBranch"`
|
||||
}
|
||||
|
||||
// RefreshType specifies how to refresh the sources of a given application
|
||||
type RefreshType string
|
||||
|
||||
@@ -1051,8 +1093,56 @@ type ApplicationStatus struct {
|
||||
SourceTypes []ApplicationSourceType `json:"sourceTypes,omitempty" protobuf:"bytes,12,opt,name=sourceTypes"`
|
||||
// ControllerNamespace indicates the namespace in which the application controller is located
|
||||
ControllerNamespace string `json:"controllerNamespace,omitempty" protobuf:"bytes,13,opt,name=controllerNamespace"`
|
||||
// SourceHydrator stores information about the current state of source hydration
|
||||
SourceHydrator SourceHydratorStatus `json:"sourceHydrator,omitempty" protobuf:"bytes,14,opt,name=sourceHydrator"`
|
||||
}
|
||||
|
||||
// SourceHydratorStatus contains information about the current state of source hydration
|
||||
type SourceHydratorStatus struct {
|
||||
// LastSuccessfulOperation holds info about the most recent successful hydration
|
||||
LastSuccessfulOperation *SuccessfulHydrateOperation `json:"lastSuccessfulOperation,omitempty" protobuf:"bytes,1,opt,name=lastSuccessfulOperation"`
|
||||
// CurrentOperation holds the status of the hydrate operation
|
||||
CurrentOperation *HydrateOperation `json:"currentOperation,omitempty" protobuf:"bytes,2,opt,name=currentOperation"`
|
||||
}
|
||||
|
||||
// HydrateOperation contains information about the most recent hydrate operation
|
||||
type HydrateOperation struct {
|
||||
// StartedAt indicates when the hydrate operation started
|
||||
StartedAt metav1.Time `json:"startedAt,omitempty" protobuf:"bytes,1,opt,name=startedAt"`
|
||||
// FinishedAt indicates when the hydrate operation finished
|
||||
FinishedAt *metav1.Time `json:"finishedAt,omitempty" protobuf:"bytes,2,opt,name=finishedAt"`
|
||||
// Phase indicates the status of the hydrate operation
|
||||
Phase HydrateOperationPhase `json:"phase" protobuf:"bytes,3,opt,name=phase"`
|
||||
// Message contains a message describing the current status of the hydrate operation
|
||||
Message string `json:"message" protobuf:"bytes,4,opt,name=message"`
|
||||
// DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation
|
||||
DrySHA string `json:"drySHA,omitempty" protobuf:"bytes,5,opt,name=drySHA"`
|
||||
// HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation
|
||||
HydratedSHA string `json:"hydratedSHA,omitempty" protobuf:"bytes,6,opt,name=hydratedSHA"`
|
||||
// SourceHydrator holds the hydrator config used for the hydrate operation
|
||||
SourceHydrator SourceHydrator `json:"sourceHydrator,omitempty" protobuf:"bytes,7,opt,name=sourceHydrator"`
|
||||
}
|
||||
|
||||
// SuccessfulHydrateOperation contains information about the most recent successful hydrate operation
|
||||
type SuccessfulHydrateOperation struct {
|
||||
// DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation
|
||||
DrySHA string `json:"drySHA,omitempty" protobuf:"bytes,5,opt,name=drySHA"`
|
||||
// HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation
|
||||
HydratedSHA string `json:"hydratedSHA,omitempty" protobuf:"bytes,6,opt,name=hydratedSHA"`
|
||||
// SourceHydrator holds the hydrator config used for the hydrate operation
|
||||
SourceHydrator SourceHydrator `json:"sourceHydrator,omitempty" protobuf:"bytes,7,opt,name=sourceHydrator"`
|
||||
}
|
||||
|
||||
// HydrateOperationPhase indicates the status of a hydrate operation
|
||||
// +kubebuilder:validation:Enum=Hydrating;Failed;Hydrated
|
||||
type HydrateOperationPhase string
|
||||
|
||||
const (
|
||||
HydrateOperationPhaseHydrating HydrateOperationPhase = "Hydrating"
|
||||
HydrateOperationPhaseFailed HydrateOperationPhase = "Failed"
|
||||
HydrateOperationPhaseHydrated HydrateOperationPhase = "Hydrated"
|
||||
)
|
||||
|
||||
func (a *ApplicationStatus) FindResource(key kube.ResourceKey) (*ResourceStatus, bool) {
|
||||
for i := range a.Resources {
|
||||
res := a.Resources[i]
|
||||
|
||||
@@ -1352,6 +1352,11 @@ func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.SourceHydrator != nil {
|
||||
in, out := &in.SourceHydrator, &out.SourceHydrator
|
||||
*out = new(SourceHydrator)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1410,6 +1415,7 @@ func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
|
||||
*out = make([]ApplicationSourceType, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.SourceHydrator.DeepCopyInto(&out.SourceHydrator)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1906,6 +1912,22 @@ func (in *ConnectionState) DeepCopy() *ConnectionState {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DrySource) DeepCopyInto(out *DrySource) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DrySource.
|
||||
func (in *DrySource) DeepCopy() *DrySource {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DrySource)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DuckTypeGenerator) DeepCopyInto(out *DuckTypeGenerator) {
|
||||
*out = *in
|
||||
@@ -2242,6 +2264,44 @@ func (in *HostResourceInfo) DeepCopy() *HostResourceInfo {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HydrateOperation) DeepCopyInto(out *HydrateOperation) {
|
||||
*out = *in
|
||||
in.StartedAt.DeepCopyInto(&out.StartedAt)
|
||||
if in.FinishedAt != nil {
|
||||
in, out := &in.FinishedAt, &out.FinishedAt
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
in.SourceHydrator.DeepCopyInto(&out.SourceHydrator)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HydrateOperation.
|
||||
func (in *HydrateOperation) DeepCopy() *HydrateOperation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HydrateOperation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HydrateTo) DeepCopyInto(out *HydrateTo) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HydrateTo.
|
||||
func (in *HydrateTo) DeepCopy() *HydrateTo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HydrateTo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in IgnoreDifferences) DeepCopyInto(out *IgnoreDifferences) {
|
||||
{
|
||||
@@ -4193,6 +4253,72 @@ func (in *SignatureKey) DeepCopy() *SignatureKey {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SourceHydrator) DeepCopyInto(out *SourceHydrator) {
|
||||
*out = *in
|
||||
out.DrySource = in.DrySource
|
||||
out.SyncSource = in.SyncSource
|
||||
if in.HydrateTo != nil {
|
||||
in, out := &in.HydrateTo, &out.HydrateTo
|
||||
*out = new(HydrateTo)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceHydrator.
|
||||
func (in *SourceHydrator) DeepCopy() *SourceHydrator {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SourceHydrator)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SourceHydratorStatus) DeepCopyInto(out *SourceHydratorStatus) {
|
||||
*out = *in
|
||||
if in.LastSuccessfulOperation != nil {
|
||||
in, out := &in.LastSuccessfulOperation, &out.LastSuccessfulOperation
|
||||
*out = new(SuccessfulHydrateOperation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CurrentOperation != nil {
|
||||
in, out := &in.CurrentOperation, &out.CurrentOperation
|
||||
*out = new(HydrateOperation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceHydratorStatus.
|
||||
func (in *SourceHydratorStatus) DeepCopy() *SourceHydratorStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SourceHydratorStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SuccessfulHydrateOperation) DeepCopyInto(out *SuccessfulHydrateOperation) {
|
||||
*out = *in
|
||||
in.SourceHydrator.DeepCopyInto(&out.SourceHydrator)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SuccessfulHydrateOperation.
|
||||
func (in *SuccessfulHydrateOperation) DeepCopy() *SuccessfulHydrateOperation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SuccessfulHydrateOperation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncOperation) DeepCopyInto(out *SyncOperation) {
|
||||
*out = *in
|
||||
@@ -4379,6 +4505,22 @@ func (in *SyncPolicyAutomated) DeepCopy() *SyncPolicyAutomated {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncSource) DeepCopyInto(out *SyncSource) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyncSource.
|
||||
func (in *SyncSource) DeepCopy() *SyncSource {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SyncSource)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncStatus) DeepCopyInto(out *SyncStatus) {
|
||||
*out = *in
|
||||
|
||||
@@ -2529,7 +2529,7 @@ func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bo
|
||||
}
|
||||
}
|
||||
|
||||
err = gitClient.Checkout(revision, submoduleEnabled)
|
||||
_, err = gitClient.Checkout(revision, submoduleEnabled)
|
||||
if err != nil {
|
||||
// When fetching with no revision, only refs/heads/* and refs/remotes/origin/* are fetched. If checkout fails
|
||||
// for the given revision, try explicitly fetching it.
|
||||
@@ -2541,7 +2541,7 @@ func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bo
|
||||
return status.Errorf(codes.Internal, "Failed to checkout revision %s: %v", revision, err)
|
||||
}
|
||||
|
||||
err = gitClient.Checkout("FETCH_HEAD", submoduleEnabled)
|
||||
_, err = gitClient.Checkout("FETCH_HEAD", submoduleEnabled)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "Failed to checkout FETCH_HEAD: %v", err)
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func newServiceWithMocks(t *testing.T, root string, signed bool) (*Service, *git
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", mock.Anything).Return(mock.Anything, nil)
|
||||
gitClient.On("CommitSHA").Return(mock.Anything, nil)
|
||||
gitClient.On("Root").Return(root)
|
||||
@@ -188,7 +188,7 @@ func newServiceWithCommitSHA(t *testing.T, root, revision string) *Service {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", revision).Return(revision, revisionErr)
|
||||
gitClient.On("CommitSHA").Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("Root").Return(root)
|
||||
@@ -3071,7 +3071,7 @@ func TestCheckoutRevisionPresentSkipFetch(t *testing.T) {
|
||||
gitClient := &gitmocks.Client{}
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", revision).Return(true)
|
||||
gitClient.On("Checkout", revision, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", revision, mock.Anything).Return("", nil)
|
||||
|
||||
err := checkoutRevision(gitClient, revision, false)
|
||||
require.NoError(t, err)
|
||||
@@ -3084,7 +3084,7 @@ func TestCheckoutRevisionNotPresentCallFetch(t *testing.T) {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", revision).Return(false)
|
||||
gitClient.On("Fetch", "").Return(nil)
|
||||
gitClient.On("Checkout", revision, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", revision, mock.Anything).Return("", nil)
|
||||
|
||||
err := checkoutRevision(gitClient, revision, false)
|
||||
require.NoError(t, err)
|
||||
@@ -3410,7 +3410,7 @@ func TestErrorGetGitDirectories(t *testing.T) {
|
||||
}, want: nil, wantErr: assert.Error},
|
||||
{name: "InvalidResolveRevision", fields: fields{service: func() *Service {
|
||||
s, _, _ := newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, paths *iomocks.TempPaths) {
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", mock.Anything).Return("", fmt.Errorf("ah error"))
|
||||
gitClient.On("Root").Return(root)
|
||||
paths.On("GetPath", mock.Anything).Return(".", nil)
|
||||
@@ -3427,7 +3427,7 @@ func TestErrorGetGitDirectories(t *testing.T) {
|
||||
}, want: nil, wantErr: assert.Error},
|
||||
{name: "ErrorVerifyCommit", fields: fields{service: func() *Service {
|
||||
s, _, _ := newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, paths *iomocks.TempPaths) {
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", mock.Anything).Return("", fmt.Errorf("ah error"))
|
||||
gitClient.On("VerifyCommitSignature", mock.Anything).Return("", fmt.Errorf("revision %s is not signed", "sadfsadf"))
|
||||
gitClient.On("Root").Return(root)
|
||||
@@ -3464,7 +3464,7 @@ func TestGetGitDirectories(t *testing.T) {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Once().Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Once().Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("Root").Return(root)
|
||||
paths.On("GetPath", mock.Anything).Return(root, nil)
|
||||
@@ -3497,7 +3497,7 @@ func TestGetGitDirectoriesWithHiddenDirSupported(t *testing.T) {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Once().Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Once().Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("Root").Return(root)
|
||||
paths.On("GetPath", mock.Anything).Return(root, nil)
|
||||
@@ -3552,7 +3552,7 @@ func TestErrorGetGitFiles(t *testing.T) {
|
||||
}, want: nil, wantErr: assert.Error},
|
||||
{name: "InvalidResolveRevision", fields: fields{service: func() *Service {
|
||||
s, _, _ := newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, paths *iomocks.TempPaths) {
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", mock.Anything).Return("", fmt.Errorf("ah error"))
|
||||
gitClient.On("Root").Return(root)
|
||||
paths.On("GetPath", mock.Anything).Return(".", nil)
|
||||
@@ -3591,7 +3591,7 @@ func TestGetGitFiles(t *testing.T) {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Once().Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Once().Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("Root").Return(root)
|
||||
gitClient.On("LsFiles", mock.Anything, mock.Anything).Once().Return(files, nil)
|
||||
@@ -3655,7 +3655,7 @@ func TestErrorUpdateRevisionForPaths(t *testing.T) {
|
||||
}, want: nil, wantErr: assert.Error},
|
||||
{name: "InvalidResolveRevision", fields: fields{service: func() *Service {
|
||||
s, _, _ := newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, paths *iomocks.TempPaths) {
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", mock.Anything).Return("", fmt.Errorf("ah error"))
|
||||
gitClient.On("Root").Return(root)
|
||||
paths.On("GetPath", mock.Anything).Return(".", nil)
|
||||
@@ -3673,7 +3673,7 @@ func TestErrorUpdateRevisionForPaths(t *testing.T) {
|
||||
}, want: nil, wantErr: assert.Error},
|
||||
{name: "InvalidResolveSyncedRevision", fields: fields{service: func() *Service {
|
||||
s, _, _ := newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, paths *iomocks.TempPaths) {
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("LsRemote", mock.Anything).Return("", fmt.Errorf("ah error"))
|
||||
gitClient.On("Root").Return(root)
|
||||
@@ -3726,7 +3726,7 @@ func TestUpdateRevisionForPaths(t *testing.T) {
|
||||
}{
|
||||
{name: "NoPathAbort", fields: func() fields {
|
||||
s, _, c := newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, paths *iomocks.TempPaths) {
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
}, ".")
|
||||
return fields{
|
||||
service: s,
|
||||
@@ -3741,7 +3741,7 @@ func TestUpdateRevisionForPaths(t *testing.T) {
|
||||
}, want: &apiclient.UpdateRevisionForPathsResponse{}, wantErr: assert.NoError},
|
||||
{name: "SameResolvedRevisionAbort", fields: func() fields {
|
||||
s, _, c := newServiceWithOpt(t, func(gitClient *gitmocks.Client, helmClient *helmmocks.Client, paths *iomocks.TempPaths) {
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("LsRemote", "SYNCEDHEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
paths.On("GetPath", mock.Anything).Return(".", nil)
|
||||
@@ -3767,7 +3767,7 @@ func TestUpdateRevisionForPaths(t *testing.T) {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("LsRemote", "SYNCEDHEAD").Once().Return("1e67a504d03def3a6a1125d934cb511680f72555", nil)
|
||||
paths.On("GetPath", mock.Anything).Return(".", nil)
|
||||
@@ -3796,7 +3796,7 @@ func TestUpdateRevisionForPaths(t *testing.T) {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("LsRemote", "SYNCEDHEAD").Once().Return("1e67a504d03def3a6a1125d934cb511680f72555", nil)
|
||||
paths.On("GetPath", mock.Anything).Return(".", nil)
|
||||
@@ -3834,7 +3834,7 @@ func TestUpdateRevisionForPaths(t *testing.T) {
|
||||
gitClient.On("Init").Return(nil)
|
||||
gitClient.On("IsRevisionPresent", mock.Anything).Return(false)
|
||||
gitClient.On("Fetch", mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
|
||||
gitClient.On("Checkout", mock.Anything, mock.Anything).Return("", nil)
|
||||
gitClient.On("LsRemote", "HEAD").Once().Return("632039659e542ed7de0c170a4fcc1c571b288fc0", nil)
|
||||
gitClient.On("LsRemote", "SYNCEDHEAD").Once().Return("1e67a504d03def3a6a1125d934cb511680f72555", nil)
|
||||
paths.On("GetPath", mock.Anything).Return(".", nil)
|
||||
|
||||
@@ -3,6 +3,7 @@ api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run
|
||||
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.41.1 serve /dex.yaml"
|
||||
redis: sh -c "/usr/local/bin/redis-server --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_BINARY_NAME=argocd-repo-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_BINARY_NAME=argocd-commit-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_COMMITSERVER_PORT:-8086}"
|
||||
ui: sh -c "test $ARGOCD_IN_CI = true && exit 0; cd ui && ARGOCD_E2E_YARN_HOST=0.0.0.0 ${ARGOCD_E2E_YARN_CMD:-yarn} start"
|
||||
reaper: ./test/container/reaper.sh
|
||||
sshd: sudo sh -c "test $ARGOCD_E2E_TEST = true && /usr/sbin/sshd -p 2222 -D -e"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: reposerver/askpass/askpass.proto
|
||||
// source: util/askpass/askpass.proto
|
||||
|
||||
package askpass
|
||||
|
||||
@@ -37,7 +37,7 @@ func (m *CredentialsRequest) Reset() { *m = CredentialsRequest{} }
|
||||
func (m *CredentialsRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*CredentialsRequest) ProtoMessage() {}
|
||||
func (*CredentialsRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_099f282cab154dba, []int{0}
|
||||
return fileDescriptor_1c7c1d31cf056104, []int{0}
|
||||
}
|
||||
func (m *CredentialsRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -85,7 +85,7 @@ func (m *CredentialsResponse) Reset() { *m = CredentialsResponse{} }
|
||||
func (m *CredentialsResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*CredentialsResponse) ProtoMessage() {}
|
||||
func (*CredentialsResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_099f282cab154dba, []int{1}
|
||||
return fileDescriptor_1c7c1d31cf056104, []int{1}
|
||||
}
|
||||
func (m *CredentialsResponse) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@@ -133,25 +133,25 @@ func init() {
|
||||
proto.RegisterType((*CredentialsResponse)(nil), "askpass.CredentialsResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("reposerver/askpass/askpass.proto", fileDescriptor_099f282cab154dba) }
|
||||
func init() { proto.RegisterFile("util/askpass/askpass.proto", fileDescriptor_1c7c1d31cf056104) }
|
||||
|
||||
var fileDescriptor_099f282cab154dba = []byte{
|
||||
// 231 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x28, 0x4a, 0x2d, 0xc8,
|
||||
0x2f, 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0xd2, 0x4f, 0x2c, 0xce, 0x2e, 0x48, 0x2c, 0x2e, 0x86, 0xd1,
|
||||
0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xec, 0x50, 0xae, 0x92, 0x16, 0x97, 0x90, 0x73, 0x51,
|
||||
0x6a, 0x4a, 0x6a, 0x5e, 0x49, 0x66, 0x62, 0x4e, 0x71, 0x50, 0x6a, 0x61, 0x69, 0x6a, 0x71, 0x89,
|
||||
0x90, 0x08, 0x17, 0x6b, 0x5e, 0x7e, 0x5e, 0x72, 0xaa, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10,
|
||||
0x84, 0xa3, 0xe4, 0xcb, 0x25, 0x8c, 0xa2, 0xb6, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0x48, 0x8a,
|
||||
0x8b, 0xa3, 0xb4, 0x38, 0xb5, 0x28, 0x2f, 0x31, 0x17, 0xa6, 0x1e, 0xce, 0x07, 0xc9, 0x81, 0xac,
|
||||
0x29, 0xcf, 0x2f, 0x4a, 0x91, 0x60, 0x82, 0xc8, 0xc1, 0xf8, 0x46, 0xf1, 0x5c, 0x7c, 0x8e, 0xc5,
|
||||
0xd9, 0x01, 0x89, 0xc5, 0xc5, 0xc1, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0xbe, 0x5c, 0x7c,
|
||||
0xee, 0xa9, 0x25, 0x48, 0x76, 0x08, 0x49, 0xeb, 0xc1, 0xdc, 0x8d, 0xe9, 0x4a, 0x29, 0x19, 0xec,
|
||||
0x92, 0x10, 0x67, 0x29, 0x31, 0x38, 0xd9, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3,
|
||||
0x83, 0x47, 0x72, 0x8c, 0x51, 0x86, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9,
|
||||
0xfa, 0x89, 0x45, 0xe9, 0xf9, 0x05, 0x45, 0xf9, 0x59, 0x60, 0x86, 0x6e, 0x72, 0x8a, 0x7e, 0x99,
|
||||
0x91, 0x3e, 0x66, 0x98, 0x25, 0xb1, 0x81, 0x03, 0xcb, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x5a,
|
||||
0x1e, 0xa9, 0xaf, 0x50, 0x01, 0x00, 0x00,
|
||||
var fileDescriptor_1c7c1d31cf056104 = []byte{
|
||||
// 225 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2a, 0x2d, 0xc9, 0xcc,
|
||||
0xd1, 0x4f, 0x2c, 0xce, 0x2e, 0x48, 0x2c, 0x2e, 0x86, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9,
|
||||
0x42, 0xec, 0x50, 0xae, 0x92, 0x16, 0x97, 0x90, 0x73, 0x51, 0x6a, 0x4a, 0x6a, 0x5e, 0x49, 0x66,
|
||||
0x62, 0x4e, 0x71, 0x50, 0x6a, 0x61, 0x69, 0x6a, 0x71, 0x89, 0x90, 0x08, 0x17, 0x6b, 0x5e, 0x7e,
|
||||
0x5e, 0x72, 0xaa, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x84, 0xa3, 0xe4, 0xcb, 0x25, 0x8c,
|
||||
0xa2, 0xb6, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0x48, 0x8a, 0x8b, 0xa3, 0xb4, 0x38, 0xb5, 0x28,
|
||||
0x2f, 0x31, 0x17, 0xa6, 0x1e, 0xce, 0x07, 0xc9, 0x81, 0xac, 0x29, 0xcf, 0x2f, 0x4a, 0x91, 0x60,
|
||||
0x82, 0xc8, 0xc1, 0xf8, 0x46, 0xf1, 0x5c, 0x7c, 0x8e, 0xc5, 0xd9, 0x01, 0x89, 0xc5, 0xc5, 0xc1,
|
||||
0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0xbe, 0x5c, 0x7c, 0xee, 0xa9, 0x25, 0x48, 0x76, 0x08,
|
||||
0x49, 0xeb, 0xc1, 0xdc, 0x8d, 0xe9, 0x4a, 0x29, 0x19, 0xec, 0x92, 0x10, 0x67, 0x29, 0x31, 0x38,
|
||||
0x59, 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x51, 0xda,
|
||||
0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x89, 0x45, 0xe9, 0xf9, 0x05,
|
||||
0x45, 0xf9, 0x59, 0x60, 0x86, 0x6e, 0x72, 0x8a, 0x7e, 0x99, 0x91, 0x3e, 0x72, 0x68, 0x25, 0xb1,
|
||||
0x81, 0x83, 0xc9, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xa8, 0xcc, 0x96, 0x87, 0x44, 0x01, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -231,7 +231,7 @@ var _AskPassService_serviceDesc = grpc.ServiceDesc{
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "reposerver/askpass/askpass.proto",
|
||||
Metadata: "util/askpass/askpass.proto",
|
||||
}
|
||||
|
||||
func (m *CredentialsRequest) Marshal() (dAtA []byte, err error) {
|
||||
@@ -1,5 +1,5 @@
|
||||
syntax = "proto3";
|
||||
option go_package = "github.com/argoproj/argo-cd/v2/reposerver/askpass";
|
||||
option go_package = "github.com/argoproj/argo-cd/v2/util/askpass";
|
||||
|
||||
package askpass;
|
||||
|
||||
@@ -11,6 +11,8 @@ const (
|
||||
ASKPASS_NONCE_ENV = "ARGOCD_GIT_ASKPASS_NONCE"
|
||||
// AKSPASS_SOCKET_PATH_ENV is the environment variable that is used to pass the socket path to the askpass script
|
||||
AKSPASS_SOCKET_PATH_ENV = "ARGOCD_ASK_PASS_SOCK"
|
||||
// CommitServerSocketPath is the path to the socket used by the commit server to communicate with the askpass server
|
||||
CommitServerSocketPath = "/tmp/commit-server-ask-pass.sock"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -69,7 +69,7 @@ type Client interface {
|
||||
Init() error
|
||||
Fetch(revision string) error
|
||||
Submodule() error
|
||||
Checkout(revision string, submoduleEnabled bool) error
|
||||
Checkout(revision string, submoduleEnabled bool) (string, error)
|
||||
LsRefs() (*Refs, error)
|
||||
LsRemote(revision string) (string, error)
|
||||
LsFiles(path string, enableNewGitFileGlobbing bool) ([]string, error)
|
||||
@@ -80,11 +80,23 @@ type Client interface {
|
||||
IsAnnotatedTag(string) bool
|
||||
ChangedFiles(revision string, targetRevision string) ([]string, error)
|
||||
IsRevisionPresent(revision string) bool
|
||||
// SetAuthor sets the author name and email in the git configuration.
|
||||
SetAuthor(name, email string) (string, error)
|
||||
// CheckoutOrOrphan checks out the branch. If the branch does not exist, it creates an orphan branch.
|
||||
CheckoutOrOrphan(branch string, submoduleEnabled bool) (string, error)
|
||||
// CheckoutOrNew checks out the given branch. If the branch does not exist, it creates an empty branch based on
|
||||
// the base branch.
|
||||
CheckoutOrNew(branch, base string, submoduleEnabled bool) (string, error)
|
||||
// RemoveContents removes all files from the git repository.
|
||||
RemoveContents() (string, error)
|
||||
// CommitAndPush commits and pushes changes to the target branch.
|
||||
CommitAndPush(branch, message string) (string, error)
|
||||
}
|
||||
|
||||
type EventHandlers struct {
|
||||
OnLsRemote func(repo string) func()
|
||||
OnFetch func(repo string) func()
|
||||
OnPush func(repo string) func()
|
||||
}
|
||||
|
||||
// nativeGitClient implements Client interface using git CLI
|
||||
@@ -459,43 +471,43 @@ func (m *nativeGitClient) Submodule() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checkout checkout specified revision
|
||||
func (m *nativeGitClient) Checkout(revision string, submoduleEnabled bool) error {
|
||||
// Checkout checks out the specified revision
|
||||
func (m *nativeGitClient) Checkout(revision string, submoduleEnabled bool) (string, error) {
|
||||
if revision == "" || revision == "HEAD" {
|
||||
revision = "origin/HEAD"
|
||||
}
|
||||
if _, err := m.runCmd("checkout", "--force", revision); err != nil {
|
||||
return err
|
||||
if out, err := m.runCmd("checkout", "--force", revision); err != nil {
|
||||
return out, fmt.Errorf("failed to checkout %s: %w", revision, err)
|
||||
}
|
||||
// We must populate LFS content by using lfs checkout, if we have at least
|
||||
// one LFS reference in the current revision.
|
||||
if m.IsLFSEnabled() {
|
||||
if largeFiles, err := m.LsLargeFiles(); err == nil {
|
||||
if len(largeFiles) > 0 {
|
||||
if _, err := m.runCmd("lfs", "checkout"); err != nil {
|
||||
return err
|
||||
if out, err := m.runCmd("lfs", "checkout"); err != nil {
|
||||
return out, fmt.Errorf("failed to checkout LFS files: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
return "", fmt.Errorf("failed to list LFS files: %w", err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(m.root + "/.gitmodules"); !os.IsNotExist(err) {
|
||||
if submoduleEnabled {
|
||||
if err := m.Submodule(); err != nil {
|
||||
return err
|
||||
return "", fmt.Errorf("failed to update submodules: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE
|
||||
// The double “f” in the arguments is not a typo: the first “f” tells
|
||||
// `git clean` to delete untracked files and directories, and the second “f”
|
||||
// tells it to clean untractked nested Git repositories (for example a
|
||||
// tells it to clean untracked nested Git repositories (for example a
|
||||
// submodule which has since been removed).
|
||||
if _, err := m.runCmd("clean", "-ffdx"); err != nil {
|
||||
return err
|
||||
if out, err := m.runCmd("clean", "-ffdx"); err != nil {
|
||||
return out, fmt.Errorf("failed to clean: %w", err)
|
||||
}
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *nativeGitClient) getRefs() ([]*plumbing.Reference, error) {
|
||||
@@ -811,6 +823,123 @@ func (m *nativeGitClient) ChangedFiles(revision string, targetRevision string) (
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// config runs a git config command.
|
||||
func (m *nativeGitClient) config(args ...string) (string, error) {
|
||||
args = append([]string{"config"}, args...)
|
||||
out, err := m.runCmd(args...)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("failed to run git config: %w", err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SetAuthor sets the author name and email in the git configuration.
|
||||
func (m *nativeGitClient) SetAuthor(name, email string) (string, error) {
|
||||
if name != "" {
|
||||
out, err := m.config("--local", "user.name", name)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
if email != "" {
|
||||
out, err := m.config("--local", "user.email", email)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// CheckoutOrOrphan checks out the branch. If the branch does not exist, it creates an orphan branch.
|
||||
func (m *nativeGitClient) CheckoutOrOrphan(branch string, submoduleEnabled bool) (string, error) {
|
||||
out, err := m.Checkout(branch, submoduleEnabled)
|
||||
if err != nil {
|
||||
// If the branch doesn't exist, create it as an orphan branch.
|
||||
if strings.Contains(err.Error(), "did not match any file(s) known to git") {
|
||||
out, err = m.runCmd("switch", "--orphan", branch)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("failed to create orphan branch: %w", err)
|
||||
}
|
||||
} else {
|
||||
return out, fmt.Errorf("failed to checkout branch: %w", err)
|
||||
}
|
||||
|
||||
// Make an empty initial commit.
|
||||
out, err = m.runCmd("commit", "--allow-empty", "-m", "Initial commit")
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("failed to commit initial commit: %w", err)
|
||||
}
|
||||
|
||||
// Push the commit.
|
||||
err = m.runCredentialedCmd("push", "origin", branch)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to push to branch: %w", err)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// CheckoutOrNew checks out the given branch. If the branch does not exist, it creates an empty branch based on
|
||||
// the base branch.
|
||||
func (m *nativeGitClient) CheckoutOrNew(branch, base string, submoduleEnabled bool) (string, error) {
|
||||
out, err := m.Checkout(branch, submoduleEnabled)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "did not match any file(s) known to git") {
|
||||
// If the branch does not exist, create any empty branch based on the sync branch
|
||||
// First, checkout the sync branch.
|
||||
out, err = m.Checkout(base, submoduleEnabled)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("failed to checkout sync branch: %w", err)
|
||||
}
|
||||
|
||||
out, err = m.runCmd("checkout", "-b", branch)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("failed to create branch: %w", err)
|
||||
}
|
||||
} else {
|
||||
return out, fmt.Errorf("failed to checkout branch: %w", err)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// RemoveContents removes all files from the git repository.
|
||||
func (m *nativeGitClient) RemoveContents() (string, error) {
|
||||
out, err := m.runCmd("rm", "-r", "--ignore-unmatch", ".")
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("failed to clear repo contents: %w", err)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// CommitAndPush commits and pushes changes to the target branch.
|
||||
func (m *nativeGitClient) CommitAndPush(branch, message string) (string, error) {
|
||||
out, err := m.runCmd("add", ".")
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("failed to add files: %w", err)
|
||||
}
|
||||
|
||||
out, err = m.runCmd("commit", "-m", message)
|
||||
if err != nil {
|
||||
if strings.Contains(out, "nothing to commit, working tree clean") {
|
||||
return out, nil
|
||||
}
|
||||
return out, fmt.Errorf("failed to commit: %w", err)
|
||||
}
|
||||
|
||||
if m.OnPush != nil {
|
||||
done := m.OnPush(m.repoURL)
|
||||
defer done()
|
||||
}
|
||||
|
||||
err = m.runCredentialedCmd("push", "origin", branch)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to push: %w", err)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// runWrapper runs a custom command with all the semantics of running the Git client
|
||||
func (m *nativeGitClient) runGnuPGWrapper(wrapper string, args ...string) (string, error) {
|
||||
cmd := exec.Command(wrapper, args...)
|
||||
@@ -850,7 +979,7 @@ func (m *nativeGitClient) runCredentialedCmd(args ...string) error {
|
||||
func (m *nativeGitClient) runCmdOutput(cmd *exec.Cmd, ropts runOpts) (string, error) {
|
||||
cmd.Dir = m.root
|
||||
cmd.Env = append(os.Environ(), cmd.Env...)
|
||||
// Set $HOME to nowhere, so we can be execute Git regardless of any external
|
||||
// Set $HOME to nowhere, so we can execute Git regardless of any external
|
||||
// authentication keys (e.g. in ~/.ssh) -- this is especially important for
|
||||
// running tests on local machines and/or CircleCI.
|
||||
cmd.Env = append(cmd.Env, "HOME=/dev/null")
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -21,6 +22,13 @@ func runCmd(workingDir string, name string, args ...string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func outputCmd(workingDir string, name string, args ...string) ([]byte, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = workingDir
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Output()
|
||||
}
|
||||
|
||||
func _createEmptyGitRepo() (string, error) {
|
||||
tempDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
@@ -365,7 +373,7 @@ func Test_nativeGitClient_Submodule(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Call Checkout() with submoduleEnabled=false.
|
||||
err = client.Checkout(commitSHA, false)
|
||||
_, err = client.Checkout(commitSHA, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check if submodule url does not exist in .git/config
|
||||
@@ -373,7 +381,7 @@ func Test_nativeGitClient_Submodule(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
|
||||
// Call Submodule() via Checkout() with submoduleEnabled=true.
|
||||
err = client.Checkout(commitSHA, true)
|
||||
_, err = client.Checkout(commitSHA, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check if the .gitmodule URL is reflected in .git/config
|
||||
@@ -479,3 +487,353 @@ func Test_nativeGitClient_RevisionMetadata(t *testing.T) {
|
||||
Message: "| Initial commit |\n\n(╯°□°)╯︵ ┻━┻",
|
||||
}, metadata)
|
||||
}
|
||||
|
||||
func Test_nativeGitClient_SetAuthor(t *testing.T) {
|
||||
expectedName := "Tester"
|
||||
expectedEmail := "test@example.com"
|
||||
|
||||
tempDir, err := _createEmptyGitRepo()
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := NewClient(fmt.Sprintf("file://%s", tempDir), NopCreds{}, true, false, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err := client.SetAuthor(expectedName, expectedEmail)
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
// Check git user.name
|
||||
gitUserName, err := outputCmd(client.Root(), "git", "config", "--local", "user.name")
|
||||
require.NoError(t, err)
|
||||
actualName := strings.TrimSpace(string(gitUserName))
|
||||
require.Equal(t, expectedName, actualName)
|
||||
|
||||
// Check git user.email
|
||||
gitUserEmail, err := outputCmd(client.Root(), "git", "config", "--local", "user.email")
|
||||
require.NoError(t, err)
|
||||
actualEmail := strings.TrimSpace(string(gitUserEmail))
|
||||
require.Equal(t, expectedEmail, actualEmail)
|
||||
}
|
||||
|
||||
func Test_nativeGitClient_CheckoutOrOrphan(t *testing.T) {
|
||||
t.Run("checkout to an existing branch", func(t *testing.T) {
|
||||
// not main or master
|
||||
expectedBranch := "feature"
|
||||
|
||||
tempDir, err := _createEmptyGitRepo()
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := NewClientExt(fmt.Sprintf("file://%s", tempDir), tempDir, NopCreds{}, true, false, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
// set the author for the initial commit of the orphan branch
|
||||
out, err := client.SetAuthor("test", "test@example.com")
|
||||
require.NoError(t, err, "error output: %s", out)
|
||||
|
||||
// get base branch
|
||||
gitCurrentBranch, err := outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
baseBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
|
||||
// get base commit
|
||||
gitCurrentCommitHash, err := outputCmd(tempDir, "git", "rev-parse", "HEAD")
|
||||
require.NoError(t, err)
|
||||
expectedCommitHash := strings.TrimSpace(string(gitCurrentCommitHash))
|
||||
|
||||
// make expected branch
|
||||
err = runCmd(tempDir, "git", "checkout", "-b", expectedBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
// checkout to base branch, ready to test
|
||||
err = runCmd(tempDir, "git", "checkout", baseBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err = client.CheckoutOrOrphan(expectedBranch, false)
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
// get current branch, verify current branch
|
||||
gitCurrentBranch, err = outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
actualBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
require.Equal(t, expectedBranch, actualBranch)
|
||||
|
||||
// get current commit hash, verify current commit hash
|
||||
// equal -> not orphan
|
||||
gitCurrentCommitHash, err = outputCmd(tempDir, "git", "rev-parse", "HEAD")
|
||||
require.NoError(t, err)
|
||||
actualCommitHash := strings.TrimSpace(string(gitCurrentCommitHash))
|
||||
require.Equal(t, expectedCommitHash, actualCommitHash)
|
||||
})
|
||||
|
||||
t.Run("orphan", func(t *testing.T) {
|
||||
// not main or master
|
||||
expectedBranch := "feature"
|
||||
|
||||
// make origin git repository
|
||||
tempDir, err := _createEmptyGitRepo()
|
||||
require.NoError(t, err)
|
||||
originGitRepoUrl := fmt.Sprintf("file://%s", tempDir)
|
||||
err = runCmd(tempDir, "git", "commit", "-m", "Second commit", "--allow-empty")
|
||||
require.NoError(t, err)
|
||||
|
||||
// get base branch
|
||||
gitCurrentBranch, err := outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
baseBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
|
||||
// make test dir
|
||||
tempDir, err = os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := NewClientExt(originGitRepoUrl, tempDir, NopCreds{}, true, false, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
// set the author for the initial commit of the orphan branch
|
||||
out, err := client.SetAuthor("test", "test@example.com")
|
||||
require.NoError(t, err, "error output: %s", out)
|
||||
|
||||
err = client.Fetch("")
|
||||
require.NoError(t, err)
|
||||
|
||||
// checkout to origin base branch
|
||||
err = runCmd(tempDir, "git", "checkout", baseBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
// get base commit
|
||||
gitCurrentCommitHash, err := outputCmd(tempDir, "git", "rev-parse", "HEAD")
|
||||
require.NoError(t, err)
|
||||
baseCommitHash := strings.TrimSpace(string(gitCurrentCommitHash))
|
||||
|
||||
out, err = client.CheckoutOrOrphan(expectedBranch, false)
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
// get current branch, verify current branch
|
||||
gitCurrentBranch, err = outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
actualBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
require.Equal(t, expectedBranch, actualBranch)
|
||||
|
||||
// check orphan branch
|
||||
|
||||
// get current commit hash, verify current commit hash
|
||||
// not equal -> orphan
|
||||
gitCurrentCommitHash, err = outputCmd(tempDir, "git", "rev-parse", "HEAD")
|
||||
require.NoError(t, err)
|
||||
currentCommitHash := strings.TrimSpace(string(gitCurrentCommitHash))
|
||||
require.NotEqual(t, baseCommitHash, currentCommitHash)
|
||||
|
||||
// get commit count on current branch, verify 1 -> orphan
|
||||
gitCommitCount, err := outputCmd(tempDir, "git", "rev-list", "--count", actualBranch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1", strings.TrimSpace(string(gitCommitCount)))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_nativeGitClient_CheckoutOrNew(t *testing.T) {
|
||||
t.Run("checkout to an existing branch", func(t *testing.T) {
|
||||
// Example status
|
||||
// * 57aef63 (feature) Second commit
|
||||
// * a4fad22 (main) Initial commit
|
||||
|
||||
// Test scenario
|
||||
// given : main branch (w/ Initial commit)
|
||||
// when : try to check out [main -> feature]
|
||||
// then : feature branch (w/ Second commit)
|
||||
|
||||
// not main or master
|
||||
expectedBranch := "feature"
|
||||
|
||||
tempDir, err := _createEmptyGitRepo()
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := NewClientExt(fmt.Sprintf("file://%s", tempDir), tempDir, NopCreds{}, true, false, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err := client.SetAuthor("test", "test@example.com")
|
||||
require.NoError(t, err, "error output: %s", out)
|
||||
|
||||
// get base branch
|
||||
gitCurrentBranch, err := outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
baseBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
|
||||
// make expected branch
|
||||
err = runCmd(tempDir, "git", "checkout", "-b", expectedBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
// make expected commit
|
||||
err = runCmd(tempDir, "git", "commit", "-m", "Second commit", "--allow-empty")
|
||||
require.NoError(t, err)
|
||||
|
||||
// get expected commit
|
||||
expectedCommitHash, err := client.CommitSHA()
|
||||
require.NoError(t, err)
|
||||
|
||||
// checkout to base branch, ready to test
|
||||
err = runCmd(tempDir, "git", "checkout", baseBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err = client.CheckoutOrNew(expectedBranch, baseBranch, false)
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
// get current branch, verify current branch
|
||||
gitCurrentBranch, err = outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
actualBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
require.Equal(t, expectedBranch, actualBranch)
|
||||
|
||||
// get current commit hash, verify current commit hash
|
||||
actualCommitHash, err := client.CommitSHA()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedCommitHash, actualCommitHash)
|
||||
})
|
||||
|
||||
t.Run("new", func(t *testing.T) {
|
||||
// Test scenario
|
||||
// given : main branch (w/ Initial commit)
|
||||
// * a4fad22 (main) Initial commit
|
||||
// when : try to check out [main -> feature]
|
||||
// then : feature branch (w/ Initial commit)
|
||||
// * a4fad22 (feature, main) Initial commit
|
||||
|
||||
// not main or master
|
||||
expectedBranch := "feature"
|
||||
|
||||
tempDir, err := _createEmptyGitRepo()
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := NewClientExt(fmt.Sprintf("file://%s", tempDir), tempDir, NopCreds{}, true, false, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err := client.SetAuthor("test", "test@example.com")
|
||||
require.NoError(t, err, "error output: %s", out)
|
||||
|
||||
// get base branch
|
||||
gitCurrentBranch, err := outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
baseBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
|
||||
// get expected commit
|
||||
expectedCommitHash, err := client.CommitSHA()
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err = client.CheckoutOrNew(expectedBranch, baseBranch, false)
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
// get current branch, verify current branch
|
||||
gitCurrentBranch, err = outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
actualBranch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
require.Equal(t, expectedBranch, actualBranch)
|
||||
|
||||
// get current commit hash, verify current commit hash
|
||||
actualCommitHash, err := client.CommitSHA()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedCommitHash, actualCommitHash)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_nativeGitClient_RemoveContents(t *testing.T) {
|
||||
// Example status
|
||||
// 2 files :
|
||||
// * <RepoRoot>/README.md
|
||||
// * <RepoRoot>/scripts/startup.sh
|
||||
|
||||
// given
|
||||
tempDir, err := _createEmptyGitRepo()
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := NewClient(fmt.Sprintf("file://%s", tempDir), NopCreds{}, true, false, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err := client.SetAuthor("test", "test@example.com")
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
err = runCmd(client.Root(), "touch", "README.md")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = runCmd(client.Root(), "mkdir", "scripts")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = runCmd(client.Root(), "touch", "scripts/startup.sh")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = runCmd(client.Root(), "git", "add", "--all")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = runCmd(client.Root(), "git", "commit", "-m", "Make files")
|
||||
require.NoError(t, err)
|
||||
|
||||
// when
|
||||
out, err = client.RemoveContents()
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
// then
|
||||
ls, err := outputCmd(client.Root(), "ls", "-l")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "total 0", strings.TrimSpace(string(ls)))
|
||||
}
|
||||
|
||||
func Test_nativeGitClient_CommitAndPush(t *testing.T) {
|
||||
tempDir, err := _createEmptyGitRepo()
|
||||
require.NoError(t, err)
|
||||
|
||||
// config receive.denyCurrentBranch updateInstead
|
||||
// because local git init make a non-bare repository which cannot be pushed normally
|
||||
err = runCmd(tempDir, "git", "config", "--local", "receive.denyCurrentBranch", "updateInstead")
|
||||
require.NoError(t, err)
|
||||
|
||||
// get branch
|
||||
gitCurrentBranch, err := outputCmd(tempDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
require.NoError(t, err)
|
||||
branch := strings.TrimSpace(string(gitCurrentBranch))
|
||||
|
||||
client, err := NewClient(fmt.Sprintf("file://%s", tempDir), NopCreds{}, true, false, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err := client.SetAuthor("test", "test@example.com")
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
err = client.Fetch(branch)
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err = client.Checkout(branch, false)
|
||||
require.NoError(t, err, "error output: ", out)
|
||||
|
||||
// make a file then commit and push
|
||||
err = runCmd(client.Root(), "touch", "README.md")
|
||||
require.NoError(t, err)
|
||||
|
||||
out, err = client.CommitAndPush(branch, "docs: README")
|
||||
require.NoError(t, err, "error output: %s", out)
|
||||
|
||||
// get current commit hash of the cloned repository
|
||||
expectedCommitHash, err := client.CommitSHA()
|
||||
require.NoError(t, err)
|
||||
|
||||
// get origin repository's current commit hash
|
||||
gitCurrentCommitHash, err := outputCmd(tempDir, "git", "rev-parse", "HEAD")
|
||||
require.NoError(t, err)
|
||||
actualCommitHash := strings.TrimSpace(string(gitCurrentCommitHash))
|
||||
require.Equal(t, expectedCommitHash, actualCommitHash)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v66/github"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
|
||||
@@ -78,6 +81,8 @@ type CredsStore interface {
|
||||
|
||||
type Creds interface {
|
||||
Environ() (io.Closer, []string, error)
|
||||
// GetUserInfo gets the username and email address for the credentials, if they're available.
|
||||
GetUserInfo(ctx context.Context) (string, string, error)
|
||||
}
|
||||
|
||||
// nop implementation
|
||||
@@ -95,16 +100,24 @@ func (c NopCreds) Environ() (io.Closer, []string, error) {
|
||||
return NopCloser{}, nil, nil
|
||||
}
|
||||
|
||||
// GetUserInfo returns empty strings for user info
|
||||
func (c NopCreds) GetUserInfo(ctx context.Context) (name string, email string, err error) {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
var _ io.Closer = NopCloser{}
|
||||
|
||||
type GenericHTTPSCreds interface {
|
||||
HasClientCert() bool
|
||||
GetClientCertData() string
|
||||
GetClientCertKey() string
|
||||
Environ() (io.Closer, []string, error)
|
||||
Creds
|
||||
}
|
||||
|
||||
var _ GenericHTTPSCreds = HTTPSCreds{}
|
||||
var (
|
||||
_ GenericHTTPSCreds = HTTPSCreds{}
|
||||
_ Creds = HTTPSCreds{}
|
||||
)
|
||||
|
||||
// HTTPS creds implementation
|
||||
type HTTPSCreds struct {
|
||||
@@ -142,6 +155,12 @@ func NewHTTPSCreds(username string, password string, clientCertData string, clie
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserInfo returns the username and email address for the credentials, if they're available.
|
||||
func (c HTTPSCreds) GetUserInfo(ctx context.Context) (string, string, error) {
|
||||
// Email not implemented for HTTPS creds.
|
||||
return c.username, "", nil
|
||||
}
|
||||
|
||||
func (c HTTPSCreds) BasicAuthHeader() string {
|
||||
h := "Authorization: Basic "
|
||||
t := c.username + ":" + c.password
|
||||
@@ -232,6 +251,8 @@ func (c HTTPSCreds) GetClientCertKey() string {
|
||||
return c.clientCertKey
|
||||
}
|
||||
|
||||
var _ Creds = SSHCreds{}
|
||||
|
||||
// SSH implementation
|
||||
type SSHCreds struct {
|
||||
sshPrivateKey string
|
||||
@@ -246,6 +267,13 @@ func NewSSHCreds(sshPrivateKey string, caPath string, insecureIgnoreHostKey bool
|
||||
return SSHCreds{sshPrivateKey, caPath, insecureIgnoreHostKey, store, proxy, noProxy}
|
||||
}
|
||||
|
||||
// GetUserInfo returns empty strings for user info.
|
||||
// TODO: Implement this method to return the username and email address for the credentials, if they're available.
|
||||
func (c SSHCreds) GetUserInfo(ctx context.Context) (string, string, error) {
|
||||
// User info not implemented for SSH creds.
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
type sshPrivateKeyFile string
|
||||
|
||||
type authFilePaths []string
|
||||
@@ -415,6 +443,37 @@ func (g GitHubAppCreds) Environ() (io.Closer, []string, error) {
|
||||
}), env, nil
|
||||
}
|
||||
|
||||
// GetUserInfo returns the username and email address for the credentials, if they're available.
|
||||
func (g GitHubAppCreds) GetUserInfo(ctx context.Context) (string, string, error) {
|
||||
// We use the apps transport to get the app slug.
|
||||
appTransport, err := g.getAppTransport()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to create GitHub app transport: %w", err)
|
||||
}
|
||||
appClient := github.NewClient(&http.Client{Transport: appTransport})
|
||||
app, _, err := appClient.Apps.Get(ctx, "")
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get app info: %w", err)
|
||||
}
|
||||
|
||||
// Then we use the installation transport to get the installation info.
|
||||
appInstallTransport, err := g.getInstallationTransport()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get app installation: %w", err)
|
||||
}
|
||||
httpClient := http.Client{Transport: appInstallTransport}
|
||||
client := github.NewClient(&httpClient)
|
||||
|
||||
appLogin := fmt.Sprintf("%s[bot]", app.GetSlug())
|
||||
user, _, err := client.Users.Get(ctx, appLogin)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get app user info: %w", err)
|
||||
}
|
||||
authorName := user.GetLogin()
|
||||
authorEmail := fmt.Sprintf("%d+%s@users.noreply.github.com", user.GetID(), user.GetLogin())
|
||||
return authorName, authorEmail, nil
|
||||
}
|
||||
|
||||
// getAccessToken fetches GitHub token using the app id, install id, and private key.
|
||||
// the token is then cached for re-use.
|
||||
func (g GitHubAppCreds) getAccessToken() (string, error) {
|
||||
@@ -422,11 +481,44 @@ func (g GitHubAppCreds) getAccessToken() (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
itr, err := g.getInstallationTransport()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create GitHub app installation transport: %w", err)
|
||||
}
|
||||
|
||||
return itr.Token(ctx)
|
||||
}
|
||||
|
||||
// getAppTransport creates a new GitHub transport for the app
|
||||
func (g GitHubAppCreds) getAppTransport() (*ghinstallation.AppsTransport, error) {
|
||||
// GitHub API url
|
||||
baseUrl := "https://api.github.com"
|
||||
if g.baseURL != "" {
|
||||
baseUrl = strings.TrimSuffix(g.baseURL, "/")
|
||||
}
|
||||
|
||||
// Create a new GitHub transport
|
||||
c := GetRepoHTTPClient(baseUrl, g.insecure, g, g.proxy, g.noProxy)
|
||||
itr, err := ghinstallation.NewAppsTransport(c.Transport,
|
||||
g.appID,
|
||||
[]byte(g.privateKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize GitHub installation transport: %w", err)
|
||||
}
|
||||
|
||||
itr.BaseURL = baseUrl
|
||||
|
||||
return itr, nil
|
||||
}
|
||||
|
||||
// getInstallationTransport creates a new GitHub transport for the app installation
|
||||
func (g GitHubAppCreds) getInstallationTransport() (*ghinstallation.Transport, error) {
|
||||
// Compute hash of creds for lookup in cache
|
||||
h := sha256.New()
|
||||
_, err := h.Write([]byte(fmt.Sprintf("%s %d %d %s", g.privateKey, g.appID, g.appInstallId, g.baseURL)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, fmt.Errorf("failed to get get SHA256 hash for GitHub app credentials: %w", err)
|
||||
}
|
||||
key := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
@@ -435,7 +527,7 @@ func (g GitHubAppCreds) getAccessToken() (string, error) {
|
||||
if found {
|
||||
itr := t.(*ghinstallation.Transport)
|
||||
// This method caches the token and if it's expired retrieves a new one
|
||||
return itr.Token(ctx)
|
||||
return itr, nil
|
||||
}
|
||||
|
||||
// GitHub API url
|
||||
@@ -452,7 +544,7 @@ func (g GitHubAppCreds) getAccessToken() (string, error) {
|
||||
[]byte(g.privateKey),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, fmt.Errorf("failed to initialize GitHub installation transport: %w", err)
|
||||
}
|
||||
|
||||
itr.BaseURL = baseUrl
|
||||
@@ -460,7 +552,7 @@ func (g GitHubAppCreds) getAccessToken() (string, error) {
|
||||
// Add transport to cache
|
||||
githubAppTokenCache.Set(key, itr, time.Minute*60)
|
||||
|
||||
return itr.Token(ctx)
|
||||
return itr, nil
|
||||
}
|
||||
|
||||
func (g GitHubAppCreds) HasClientCert() bool {
|
||||
@@ -475,6 +567,8 @@ func (g GitHubAppCreds) GetClientCertKey() string {
|
||||
return g.clientCertKey
|
||||
}
|
||||
|
||||
var _ Creds = GoogleCloudCreds{}
|
||||
|
||||
// GoogleCloudCreds to authenticate to Google Cloud Source repositories
|
||||
type GoogleCloudCreds struct {
|
||||
creds *google.Credentials
|
||||
@@ -490,6 +584,16 @@ func NewGoogleCloudCreds(jsonData string, store CredsStore) GoogleCloudCreds {
|
||||
return GoogleCloudCreds{creds, store}
|
||||
}
|
||||
|
||||
// GetUserInfo returns the username and email address for the credentials, if they're available.
|
||||
// TODO: implement getting email instead of just username.
|
||||
func (c GoogleCloudCreds) GetUserInfo(ctx context.Context) (string, string, error) {
|
||||
username, err := c.getUsername()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get username from creds: %w", err)
|
||||
}
|
||||
return username, "", nil
|
||||
}
|
||||
|
||||
func (c GoogleCloudCreds) Environ() (io.Closer, []string, error) {
|
||||
username, err := c.getUsername()
|
||||
if err != nil {
|
||||
|
||||
@@ -320,7 +320,7 @@ func TestLFSClient(t *testing.T) {
|
||||
err = client.Fetch("")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Checkout(commitSHA, true)
|
||||
_, err = client.Checkout(commitSHA, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
largeFiles, err := client.LsLargeFiles()
|
||||
@@ -358,7 +358,7 @@ func TestVerifyCommitSignature(t *testing.T) {
|
||||
commitSHA, err := client.LsRemote("HEAD")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Checkout(commitSHA, true)
|
||||
_, err = client.Checkout(commitSHA, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 28027897aad1262662096745f2ce2d4c74d02b7f is a commit that is signed in the repo
|
||||
@@ -415,7 +415,7 @@ func TestNewFactory(t *testing.T) {
|
||||
err = client.Fetch("")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Checkout(commitSHA, true)
|
||||
_, err = client.Checkout(commitSHA, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
revisionMetadata, err := client.RevisionMetadata(commitSHA)
|
||||
|
||||
160
util/git/mocks/Client.go
generated
160
util/git/mocks/Client.go
generated
@@ -43,21 +43,115 @@ func (_m *Client) ChangedFiles(revision string, targetRevision string) ([]string
|
||||
}
|
||||
|
||||
// Checkout provides a mock function with given fields: revision, submoduleEnabled
|
||||
func (_m *Client) Checkout(revision string, submoduleEnabled bool) error {
|
||||
func (_m *Client) Checkout(revision string, submoduleEnabled bool) (string, error) {
|
||||
ret := _m.Called(revision, submoduleEnabled)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Checkout")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) (string, error)); ok {
|
||||
return rf(revision, submoduleEnabled)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, bool) string); ok {
|
||||
r0 = rf(revision, submoduleEnabled)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
|
||||
r1 = rf(revision, submoduleEnabled)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CheckoutOrNew provides a mock function with given fields: branch, base, submoduleEnabled
|
||||
func (_m *Client) CheckoutOrNew(branch string, base string, submoduleEnabled bool) (string, error) {
|
||||
ret := _m.Called(branch, base, submoduleEnabled)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CheckoutOrNew")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, bool) (string, error)); ok {
|
||||
return rf(branch, base, submoduleEnabled)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, bool) string); ok {
|
||||
r0 = rf(branch, base, submoduleEnabled)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, bool) error); ok {
|
||||
r1 = rf(branch, base, submoduleEnabled)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CheckoutOrOrphan provides a mock function with given fields: branch, submoduleEnabled
|
||||
func (_m *Client) CheckoutOrOrphan(branch string, submoduleEnabled bool) (string, error) {
|
||||
ret := _m.Called(branch, submoduleEnabled)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CheckoutOrOrphan")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) (string, error)); ok {
|
||||
return rf(branch, submoduleEnabled)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, bool) string); ok {
|
||||
r0 = rf(branch, submoduleEnabled)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
|
||||
r1 = rf(branch, submoduleEnabled)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CommitAndPush provides a mock function with given fields: branch, message
|
||||
func (_m *Client) CommitAndPush(branch string, message string) (string, error) {
|
||||
ret := _m.Called(branch, message)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CommitAndPush")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) (string, error)); ok {
|
||||
return rf(branch, message)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) string); ok {
|
||||
r0 = rf(branch, message)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(branch, message)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CommitSHA provides a mock function with given fields:
|
||||
@@ -278,6 +372,34 @@ func (_m *Client) LsRemote(revision string) (string, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RemoveContents provides a mock function with given fields:
|
||||
func (_m *Client) RemoveContents() (string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RemoveContents")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RevisionMetadata provides a mock function with given fields: revision
|
||||
func (_m *Client) RevisionMetadata(revision string) (*git.RevisionMetadata, error) {
|
||||
ret := _m.Called(revision)
|
||||
@@ -326,6 +448,34 @@ func (_m *Client) Root() string {
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetAuthor provides a mock function with given fields: name, email
|
||||
func (_m *Client) SetAuthor(name string, email string) (string, error) {
|
||||
ret := _m.Called(name, email)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetAuthor")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) (string, error)); ok {
|
||||
return rf(name, email)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) string); ok {
|
||||
r0 = rf(name, email)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(name, email)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Submodule provides a mock function with given fields:
|
||||
func (_m *Client) Submodule() error {
|
||||
ret := _m.Called()
|
||||
|
||||
25
util/io/files/secure_mkdir_default.go
Normal file
25
util/io/files/secure_mkdir_default.go
Normal file
@@ -0,0 +1,25 @@
|
||||
//go:build !linux
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
)
|
||||
|
||||
// SecureMkdirAll creates a directory with the given mode and returns the full path to the directory. It prevents
|
||||
// directory traversal attacks by ensuring the path is within the root directory. The path is constructed as if the
|
||||
// given root is the root of the filesystem. So anything traversing outside the root is simply removed from the path.
|
||||
func SecureMkdirAll(root, unsafePath string, mode os.FileMode) (string, error) {
|
||||
fullPath, err := securejoin.SecureJoin(root, unsafePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to construct secure path: %w", err)
|
||||
}
|
||||
err = os.MkdirAll(fullPath, mode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
return fullPath, nil
|
||||
}
|
||||
25
util/io/files/secure_mkdir_linux.go
Normal file
25
util/io/files/secure_mkdir_linux.go
Normal file
@@ -0,0 +1,25 @@
|
||||
//go:build linux
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
)
|
||||
|
||||
// SecureMkdirAll creates a directory with the given mode and returns the full path to the directory. It prevents
|
||||
// directory traversal attacks by ensuring the path is within the root directory. The path is constructed as if the
|
||||
// given root is the root of the filesystem. So anything traversing outside the root is simply removed from the path.
|
||||
func SecureMkdirAll(root, unsafePath string, mode os.FileMode) (string, error) {
|
||||
err := securejoin.MkdirAll(root, unsafePath, int(mode))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to make directory: %w", err)
|
||||
}
|
||||
fullPath, err := securejoin.SecureJoin(root, unsafePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to construct secure path: %w", err)
|
||||
}
|
||||
return fullPath, nil
|
||||
}
|
||||
67
util/io/files/secure_mkdir_test.go
Normal file
67
util/io/files/secure_mkdir_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSecureMkdirAllDefault(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
unsafePath := "test/dir"
|
||||
fullPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedPath := path.Join(root, unsafePath)
|
||||
assert.Equal(t, expectedPath, fullPath)
|
||||
}
|
||||
|
||||
func TestSecureMkdirAllWithExistingDir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
unsafePath := "existing/dir"
|
||||
|
||||
fullPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
|
||||
newPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, fullPath, newPath)
|
||||
}
|
||||
|
||||
func TestSecureMkdirAllWithFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
unsafePath := "file.txt"
|
||||
|
||||
filePath := filepath.Join(root, unsafePath)
|
||||
err := os.WriteFile(filePath, []byte("test"), os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should fail because there is a file at the path
|
||||
_, err = SecureMkdirAll(root, unsafePath, os.ModePerm)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSecureMkdirAllDotDotPath(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
unsafePath := "../outside"
|
||||
|
||||
fullPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedPath := filepath.Join(root, "outside")
|
||||
assert.Equal(t, expectedPath, fullPath)
|
||||
|
||||
info, err := os.Stat(fullPath)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, info.IsDir())
|
||||
|
||||
relPath, err := filepath.Rel(root, fullPath)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, strings.HasPrefix(relPath, ".."))
|
||||
}
|
||||
Reference in New Issue
Block a user