Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Change platform config format for Git clients #1238

Merged
merged 2 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions docs/docs/api/config/v1alpha1.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ _Appears in:_
| Field | Description |
| --- | --- |
| `auths` _[GitAuth](#gitauth) array_ | Auths the git client can behave as. |
| `gitHubAuths` _[GitHub](#github) array_ | GitHubAuths is authentication information for GitHub repositories |
| `gitLabAuths` _[GitLab](#gitlab) array_ | |
| `gitHubAuths` _[GitHub](#github) array_ | GitHubAuths is authentication information for GitHub repositories. |
| `gitLabAuths` _[GitLab](#gitlab) array_ | GitLabAuths is the authentication information for GitLab repositories. |
| `author` _[GitAuthor](#gitauthor)_ | Author used when creating commits. |


Expand Down Expand Up @@ -353,7 +353,8 @@ _Appears in:_
| Field | Description |
| --- | --- |
| `url` _string_ | URL is a exact match for the repo-url this auth can be used for. |
| `urlPrefix` _string_ | URLPrefix is a prefix-match for the repo urls this auth can be used for. |
| `urlPrefix` _string_ | URLPrefix is a prefix-match for the repo urls this auth can be used for.<br />Deprecated: use Match instead |
| `match` _[URLMatch](#urlmatch)_ | How the url should be matched. Can either be 'exact' or 'prefix'<br />Defaults to 'exact' |
| `credentials` _[GitCredentials](#gitcredentials)_ | Credentials to use when connecting to git. |
| `pullingIntervalSeconds` _integer_ | If no web hook is confugured, pull the git repository at the set interval instead<br />to fetch changes. Defaults to 3 mins if no value. |

Expand Down Expand Up @@ -408,8 +409,9 @@ _Appears in:_

| Field | Description |
| --- | --- |
| `organization` _string_ | Organization is the GitHub organization to match. |
| `repository` _string_ | Repository matches the GitHub repository. If empty, matches all. |
| `orgRepo` _string_ | OrgRepo is a string containing the GitHub organization and optionally a repository as well.<br />If both org and repo is given, they should be seperated by a '/', e.g. 'myorg/myrepo'.<br />If repo is not given, e.g. 'myrepo', then it matches all repositories within the org 'myorg'.<br />If both org and repo is given, it matches exactly the repo within the org. |
| `organization` _string_ | Organization is the GitHub organization to match.<br />Deprecated: Use OrgRepo instead |
| `repository` _string_ | Repository matches the GitHub repository. If empty, matches all.<br />Deprecated: Use OrgRepo instead |
| `auth` _[GitHubAuth](#githubauth)_ | Auth contains GitHub specific authentication configuration. |
| `polling` _[GitHubPolling](#githubpolling)_ | Polling contains GitHub specific configuration. |

Expand Down Expand Up @@ -464,8 +466,9 @@ _Appears in:_

| Field | Description |
| --- | --- |
| `groups` _string array_ | Groups is a sequence of GitLab groups.<br />The first is the main group and the rest a nesting of subgroups.<br />If Project is empty, the configuration will match any<br />GitLab repository whose (group, subgroups) sequence where 'groups' is a prefix. |
| `project` _string_ | Project is the GitLab project of the repository. Can be empty for matching all project names. |
| `groupsProject` _string_ | GroupsProject is a string containing a list of GitLab groups and optionally a project<br />Groups are separated by '/' and project by ':', e.g.<br />group/subgroup1/subgroup2:project<br />If a project is given, it matches exactly that project within that sequence of subsgroups<br />If no project is given, it matches all projects within all subgroups which are children of the<br />given group sequence. E.g.<br />'group' will match 'group/subgroup1:project1' and 'group/subgroup1/subgroup2:project2' |
| `groups` _string array_ | Groups is a sequence of GitLab groups.<br />The first is the main group and the rest a nesting of subgroups.<br />If Project is empty, the configuration will match any<br />GitLab repository whose (group, subgroups) sequence where 'groups' is a prefix.<br />Deprecated: Use GroupsProject |
| `project` _string_ | Project is the GitLab project of the repository. Can be empty for matching all project names.<br />Deprecated: Use GroupsProject |
| `auth` _[GitLabAuth](#gitlabauth)_ | Auth contains GitLab specific authentication configuration. |
| `polling` _[GitLabPolling](#gitlabpolling)_ | Polling contains GitLab specific configuration. |

Expand Down Expand Up @@ -747,6 +750,17 @@ _Appears in:_
| `enableForPlatform` _boolean_ | If set, will enable the step for the Rig platform which is a Capsule as well<br />Deprecated, use Match.EnableForPlatform. |


### URLMatch

_Underlying type:_ _string_



_Appears in:_
- [GitAuth](#gitauth)






Expand Down
14 changes: 4 additions & 10 deletions docs/docs/operator-manual/gitops.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,11 @@ rig:
private_key: <pem-encoded-private-key>
gitHubAuths:
# Matches git@github.com:myorg/myrepository2.git and enables WebHook
- organization: myorg
repository: myrepository2
- orgRepo: myorg/myrepository2
polling:
webhookSecret: secret123
# Matches https://github.com/myorg/myrepository3.git, uses GitHub authentication and pulls every 60 seconds.
- organization: myorg
repository: myrepository3
- orgRepo: myorg/myrepository3
auth:
appID: 1234
installationID: 12345
Expand All @@ -101,11 +99,7 @@ rig:
pullingIntervalSeconds: 60
gitLabAuths
# Matches git@gitlab.com:mygroup/subgroup1/subgroup2/myproject.git, uses GitLab authentication and enables WebHook.
- groups:
- mygroup
- subgroup1
- subgroup2
project: myproject
- groupsProject: mygroup/subgroup1/subgroup2:myproject
auth:
accessToken: MY_ACCESS_TOKEN
polling:
Expand All @@ -119,7 +113,7 @@ When computing the configuration for a repository, Rig first searches the provid
either polling or authentication sat, it will provide the missing configuration from the first matching general git config (in `client.git.auths`).

#### GitHub Authentication
[Here](/api/config/v1alpha1#github) you can see the definition of our GitHub configuration. The Organization and Repository uniquely defines a GitHub repository. We support GitHub specific authentication using [GitHub Apps](https://docs.github.com/en/apps).
[Here](/api/config/v1alpha1#github) you can see the definition of our GitHub configuration. `OrgRepo` uniquely defines a GitHub repository. We support GitHub specific authentication using [GitHub Apps](https://docs.github.com/en/apps).
You have to make a new GitHub App for either your organization or account. Go through `Settings` -> `Developer Settings` -> `GitHub Apps` -> `New GitHub App`. Give the App a name and HomePage URL (neither of which Rig uses but GitHub requires them). Under `Permissions` and `Repository Permissions`, you'll have to give the app Read/Write access to `Contents`.

After creating the app, go back to the App overview page under `Developer Settings`. Click `Edit` for your newly created app, then Install App and choose your organization or user. After being installed, you can find the app under `Third-party Access` / `GitHub Apps`. Clicking `Configure` takes you to the installation page.
Expand Down
182 changes: 70 additions & 112 deletions pkg/api/config/v1alpha1/platform_config_types.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package v1alpha1

import (
"fmt"
"strings"

"github.com/xeipuuv/gojsonschema"
"go.uber.org/zap/zapcore"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
)

func init() {
Expand Down Expand Up @@ -355,12 +353,13 @@ type PathPrefixes struct {
// for GitHubAuths if there is a match.
type ClientGit struct {
// Auths the git client can behave as.
Auths []GitAuth `json:"auths,omitempty"`
Auths []GitAuth `json:"auths,omitempty" patchStrategy:"merge" patchMergeKey:"url"`

// GitHubAuths is authentication information for GitHub repositories
GitHubAuths []GitHub `json:"gitHubAuths,omitempty"`
// GitHubAuths is authentication information for GitHub repositories.
GitHubAuths []GitHub `json:"gitHubAuths,omitempty" patchStrategy:"merge" patchMergeKey:"orgRepo"`

GiLabAuths []GitLab `json:"gitLabAuths,omitempty"`
// GitLabAuths is the authentication information for GitLab repositories.
GitLabAuths []GitLab `json:"gitLabAuths,omitempty" patchStrategy:"merge" patchMergeKey:"groupsProject"`

// Author used when creating commits.
Author GitAuthor `json:"author,omitempty"`
Expand All @@ -371,8 +370,13 @@ type GitAuth struct {
URL string `json:"url,omitempty"`

// URLPrefix is a prefix-match for the repo urls this auth can be used for.
// Deprecated: use Match instead
URLPrefix string `json:"urlPrefix,omitempty"`

// How the url should be matched. Can either be 'exact' or 'prefix'
// Defaults to 'exact'
Match URLMatch `json:"match,omitempty"`

// Credentials to use when connecting to git.
Credentials GitCredentials `json:"credentials,omitempty"`

Expand All @@ -381,6 +385,13 @@ type GitAuth struct {
PullingIntervalSeconds int `json:"pullingIntervalSeconds"`
}

type URLMatch string

const (
URLMatchExact URLMatch = "exact"
URLMatchPrefix URLMatch = "prefix"
)

// GitHub contains configuration specifically for GitHub repositories.
// To enable pull requests on a GitHub repository, you must add GitHub authentication
// using appID, installationID and privateKey for a GitHub app with read/write access to
Expand All @@ -390,10 +401,18 @@ type GitAuth struct {
// If you have GitHub app authentication for a GitHub app with read/write access to the repository,
// you don't need a matching GitAuth section.
type GitHub struct {
// OrgRepo is a string containing the GitHub organization and optionally a repository as well.
// If both org and repo is given, they should be seperated by a '/', e.g. 'myorg/myrepo'.
// If repo is not given, e.g. 'myrepo', then it matches all repositories within the org 'myorg'.
// If both org and repo is given, it matches exactly the repo within the org.
OrgRepo string `json:"orgRepo"`

// Organization is the GitHub organization to match.
// Deprecated: Use OrgRepo instead
Organization string `json:"organization"`

// Repository matches the GitHub repository. If empty, matches all.
// Deprecated: Use OrgRepo instead
Repository string `json:"repository,omitempty"`

// Auth contains GitHub specific authentication configuration.
Expand All @@ -403,6 +422,21 @@ type GitHub struct {
Polling GitHubPolling `json:"polling,omitempty"`
}

func (g GitHub) GetOrgRepo() (string, string) {
splits := strings.SplitN(g.OrgRepo, "/", 2)
if len(splits) == 2 {
return splits[0], splits[1]
}
return splits[0], ""
}

func MakeOrgRepo(org, repo string) string {
if repo == "" {
return org
}
return org + "/" + repo
}

// GitHubAuth contains authentication information specifically for a GitHub repository.
// Authentication is done using GitHub apps. See https://docs.rig.dev/operator-manual/gitops#github-authentication
// for a guide on how to set it up.
Expand Down Expand Up @@ -437,13 +471,24 @@ type GitHubPolling struct {
// if there is a GitAuth section with credentials for the given repository instead.
// If you have GitLab authentication for a repository, you don't need a matching GitAuth section.
type GitLab struct {
// GroupsProject is a string containing a list of GitLab groups and optionally a project
// Groups are separated by '/' and project by ':', e.g.
// group/subgroup1/subgroup2:project
// If a project is given, it matches exactly that project within that sequence of subsgroups
// If no project is given, it matches all projects within all subgroups which are children of the
// given group sequence. E.g.
// 'group' will match 'group/subgroup1:project1' and 'group/subgroup1/subgroup2:project2'
GroupsProject string `json:"groupsProject"`

// Groups is a sequence of GitLab groups.
// The first is the main group and the rest a nesting of subgroups.
// If Project is empty, the configuration will match any
// GitLab repository whose (group, subgroups) sequence where 'groups' is a prefix.
// Deprecated: Use GroupsProject
Groups []string `json:"groups,omitempty"`

// Project is the GitLab project of the repository. Can be empty for matching all project names.
// Deprecated: Use GroupsProject
Project string `json:"project,omitempty"`

// Auth contains GitLab specific authentication configuration.
Expand All @@ -453,6 +498,24 @@ type GitLab struct {
Polling GitLabPolling `json:"polling,omitempty"`
}

func (g GitLab) GetGroupsProject() ([]string, string) {
splits := strings.SplitN(g.GroupsProject, ":", 2)
groups := strings.Split(splits[0], "/")
var project string
if len(splits) == 2 {
project = splits[1]
}
return groups, project
}

func MakeGroupsProject(groups []string, project string) string {
s := strings.Join(groups, "/")
if project != "" {
s += ":" + project
}
return s
}

// GitLabAuth contains authentication information specifically for a GitLab repository.
// Authentication is done using an access token. See https://docs.rig.dev/operator-manual/gitops#gitlab-authentication
// for a guide on how to set it up.
Expand Down Expand Up @@ -628,108 +691,3 @@ func NewDefaultPlatform() *PlatformConfig {

return cfg
}

func (cfg *PlatformConfig) Validate() error {
if cfg.Cluster.Type != "" && len(cfg.Clusters) != 0 {
return fmt.Errorf("only one of `cluster` and `clusters` must be set")
}

var errs field.ErrorList
errs = append(errs, cfg.Cluster.validate(field.NewPath("clusters"))...)
errs = append(errs, cfg.validateCapsuleExtensions(field.NewPath("capsuleExtensions"))...)

return errs.ToAggregate()
}

func (c Cluster) validate(path *field.Path) field.ErrorList {
return c.Git.validate(path.Child("git"))
}

func (g ClusterGit) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList
if g.PathPrefix != "" && g.PathPrefixes != (PathPrefixes{}) {
return append(errs, field.Invalid(path, g, "can't set both `pathPrefix` and `pathPrefixes`"))
}

return errs
}

func (cfg *PlatformConfig) validateCapsuleExtensions(path *field.Path) field.ErrorList {
var errs field.ErrorList
for k, v := range cfg.CapsuleExtensions {
if err := v.validate(path.Key(k)); err != nil {
errs = append(errs, err...)
}
}
return errs
}

func (e Extension) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList

schemaPath := path.Child("schema")
if e.Schema == nil {
errs = append(errs, field.Invalid(
path, e, "value has no schema"),
)
return errs
}
if e.Schema.Type != "object" {
errs = append(errs, field.Invalid(
schemaPath.Child("type"), e.Schema.Type, "top level schema must be of type 'object'"),
)
return errs
}

for key, prop := range e.Schema.Properties {
if prop.Type == "object" || prop.Type == "array" {
errs = append(errs,
field.Invalid(
schemaPath.Child("properties").Key(key).Child("type"),
prop.Type, "complex child properties are not yet supported"),
)
}
}

if _, err := gojsonschema.NewSchema(gojsonschema.NewGoLoader(e.Schema)); err != nil {
errs = append(errs, field.Invalid(schemaPath, e.Schema, err.Error()))
}

return errs
}

func (cfg *PlatformConfig) Migrate() {
for _, c := range cfg.Clusters {
if c.Git.URL != "" && c.Git.Credentials != (GitCredentials{}) {
cfg.Client.Git.Auths = append(cfg.Client.Git.Auths, GitAuth{
URL: c.Git.URL,
Credentials: c.Git.Credentials,
})
}
if cfg.Client.Git.Author.Name == "" {
cfg.Client.Git.Author.Name = c.Git.Author.Name
cfg.Client.Git.Author.Email = c.Git.Author.Email
}
}

if cfg.Client.Mailjet != (ClientMailjet{}) {
cfg.Client.Mailjets["mailjet"] = cfg.Client.Mailjet
}

if cfg.Client.SMTP != (ClientSMTP{}) {
cfg.Client.SMTPs["smtp"] = cfg.Client.SMTP
}

if cfg.Email != (Email{}) && cfg.Email.Type != "" {
switch cfg.Email.Type {
case EmailTypeMailjet:
if cfg.Email.ID == "" {
cfg.Email.ID = "mailjet"
}
case EmailTypeSMTP:
if cfg.Email.ID == "" {
cfg.Email.ID = "smtp"
}
}
}
}
Loading
Loading