From 2235b7efb4f3b844c53dff6557c40d1e1fbbf45f Mon Sep 17 00:00:00 2001 From: Sachin Date: Mon, 11 Mar 2024 12:40:56 +0000 Subject: [PATCH 1/2] feat: Add github membership --- README.md | 1 + .../v1alpha1/zz_generated.conversion_hubs.go | 14 + apis/user/v1alpha1/zz_generated.deepcopy.go | 207 ++++++++++ apis/user/v1alpha1/zz_generated.managed.go | 68 ++++ .../user/v1alpha1/zz_generated.managedlist.go | 17 + apis/user/v1alpha1/zz_groupversion_info.go | 36 ++ .../v1alpha1/zz_membership_terraformed.go | 133 ++++++ apis/user/v1alpha1/zz_membership_types.go | 147 +++++++ apis/zz_register.go | 2 + config/external_name.go | 2 + config/membership/config.go | 12 + config/provider.go | 2 + .../user/v1alpha1/membership.yaml | 12 + .../user/membership/zz_controller.go | 78 ++++ internal/controller/zz_setup.go | 2 + .../user.github.upbound.io_memberships.yaml | 379 ++++++++++++++++++ tests/cases/membership/membership.yaml | 12 + 17 files changed, 1124 insertions(+) create mode 100755 apis/user/v1alpha1/zz_generated.conversion_hubs.go create mode 100644 apis/user/v1alpha1/zz_generated.deepcopy.go create mode 100644 apis/user/v1alpha1/zz_generated.managed.go create mode 100644 apis/user/v1alpha1/zz_generated.managedlist.go create mode 100755 apis/user/v1alpha1/zz_groupversion_info.go create mode 100755 apis/user/v1alpha1/zz_membership_terraformed.go create mode 100755 apis/user/v1alpha1/zz_membership_types.go create mode 100644 config/membership/config.go create mode 100644 examples-generated/user/v1alpha1/membership.yaml create mode 100755 internal/controller/user/membership/zz_controller.go create mode 100644 package/crds/user.github.upbound.io_memberships.yaml create mode 100644 tests/cases/membership/membership.yaml diff --git a/README.md b/README.md index 764d2d8..07eb553 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ spec: | `TeamRepository` | `team` | `github_team_repository` | | | `EmuTeamMapping` | `team` | `github_emu_group_mapping` | | | `ActionsSecrets` | `actions` | `github_actions_secret` | | +| `Membership` | `user` | `github_membership` | Works only with - GitHub App user access tokens - GitHub App installation access tokens - Fine-grained personal access tokens using a token with at least `members:read` | | `Organization` | `enterprise` | `github_enterprise_organization` | Works only with PAT based authentication using a token with at least `admin:enterprise` scope.
The following *GraphQL* query can be used to obtain the required `enterprise_id`:
`gh api graphql -f query='query ($slug: String!) { enterprise(slug: $slug) { id } }' -F slug='' --jq '.data.enterprise.id'` | ## Adding resources diff --git a/apis/user/v1alpha1/zz_generated.conversion_hubs.go b/apis/user/v1alpha1/zz_generated.conversion_hubs.go new file mode 100755 index 0000000..ca6d990 --- /dev/null +++ b/apis/user/v1alpha1/zz_generated.conversion_hubs.go @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +/* +Copyright 2022 Upbound Inc. +*/ + +// Code generated by upjet. DO NOT EDIT. + +package v1alpha1 + +// Hub marks this type as a conversion hub. +func (tr *Membership) Hub() {} diff --git a/apis/user/v1alpha1/zz_generated.deepcopy.go b/apis/user/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..6076b11 --- /dev/null +++ b/apis/user/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,207 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2022 Upbound Inc. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Membership) DeepCopyInto(out *Membership) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Membership. +func (in *Membership) DeepCopy() *Membership { + if in == nil { + return nil + } + out := new(Membership) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Membership) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MembershipInitParameters) DeepCopyInto(out *MembershipInitParameters) { + *out = *in + if in.DowngradeOnDestroy != nil { + in, out := &in.DowngradeOnDestroy, &out.DowngradeOnDestroy + *out = new(bool) + **out = **in + } + if in.Role != nil { + in, out := &in.Role, &out.Role + *out = new(string) + **out = **in + } + if in.Username != nil { + in, out := &in.Username, &out.Username + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MembershipInitParameters. +func (in *MembershipInitParameters) DeepCopy() *MembershipInitParameters { + if in == nil { + return nil + } + out := new(MembershipInitParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MembershipList) DeepCopyInto(out *MembershipList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Membership, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MembershipList. +func (in *MembershipList) DeepCopy() *MembershipList { + if in == nil { + return nil + } + out := new(MembershipList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MembershipList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MembershipObservation) DeepCopyInto(out *MembershipObservation) { + *out = *in + if in.DowngradeOnDestroy != nil { + in, out := &in.DowngradeOnDestroy, &out.DowngradeOnDestroy + *out = new(bool) + **out = **in + } + if in.Etag != nil { + in, out := &in.Etag, &out.Etag + *out = new(string) + **out = **in + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Role != nil { + in, out := &in.Role, &out.Role + *out = new(string) + **out = **in + } + if in.Username != nil { + in, out := &in.Username, &out.Username + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MembershipObservation. +func (in *MembershipObservation) DeepCopy() *MembershipObservation { + if in == nil { + return nil + } + out := new(MembershipObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MembershipParameters) DeepCopyInto(out *MembershipParameters) { + *out = *in + if in.DowngradeOnDestroy != nil { + in, out := &in.DowngradeOnDestroy, &out.DowngradeOnDestroy + *out = new(bool) + **out = **in + } + if in.Role != nil { + in, out := &in.Role, &out.Role + *out = new(string) + **out = **in + } + if in.Username != nil { + in, out := &in.Username, &out.Username + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MembershipParameters. +func (in *MembershipParameters) DeepCopy() *MembershipParameters { + if in == nil { + return nil + } + out := new(MembershipParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MembershipSpec) DeepCopyInto(out *MembershipSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) + in.InitProvider.DeepCopyInto(&out.InitProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MembershipSpec. +func (in *MembershipSpec) DeepCopy() *MembershipSpec { + if in == nil { + return nil + } + out := new(MembershipSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MembershipStatus) DeepCopyInto(out *MembershipStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MembershipStatus. +func (in *MembershipStatus) DeepCopy() *MembershipStatus { + if in == nil { + return nil + } + out := new(MembershipStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apis/user/v1alpha1/zz_generated.managed.go b/apis/user/v1alpha1/zz_generated.managed.go new file mode 100644 index 0000000..19b9c52 --- /dev/null +++ b/apis/user/v1alpha1/zz_generated.managed.go @@ -0,0 +1,68 @@ +/* +Copyright 2022 Upbound Inc. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + +// GetCondition of this Membership. +func (mg *Membership) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this Membership. +func (mg *Membership) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetManagementPolicies of this Membership. +func (mg *Membership) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Membership. +func (mg *Membership) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +// GetPublishConnectionDetailsTo of this Membership. +func (mg *Membership) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { + return mg.Spec.PublishConnectionDetailsTo +} + +// GetWriteConnectionSecretToReference of this Membership. +func (mg *Membership) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Membership. +func (mg *Membership) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this Membership. +func (mg *Membership) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetManagementPolicies of this Membership. +func (mg *Membership) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Membership. +func (mg *Membership) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +// SetPublishConnectionDetailsTo of this Membership. +func (mg *Membership) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { + mg.Spec.PublishConnectionDetailsTo = r +} + +// SetWriteConnectionSecretToReference of this Membership. +func (mg *Membership) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/user/v1alpha1/zz_generated.managedlist.go b/apis/user/v1alpha1/zz_generated.managedlist.go new file mode 100644 index 0000000..38937c7 --- /dev/null +++ b/apis/user/v1alpha1/zz_generated.managedlist.go @@ -0,0 +1,17 @@ +/* +Copyright 2022 Upbound Inc. +*/ +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import resource "github.com/crossplane/crossplane-runtime/pkg/resource" + +// GetItems of this MembershipList. +func (l *MembershipList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} diff --git a/apis/user/v1alpha1/zz_groupversion_info.go b/apis/user/v1alpha1/zz_groupversion_info.go new file mode 100755 index 0000000..023a3b3 --- /dev/null +++ b/apis/user/v1alpha1/zz_groupversion_info.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +/* +Copyright 2022 Upbound Inc. +*/ + +// Code generated by upjet. DO NOT EDIT. + +// +kubebuilder:object:generate=true +// +groupName=user.github.upbound.io +// +versionName=v1alpha1 +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +// Package type metadata. +const ( + CRDGroup = "user.github.upbound.io" + CRDVersion = "v1alpha1" +) + +var ( + // CRDGroupVersion is the API Group Version used to register the objects + CRDGroupVersion = schema.GroupVersion{Group: CRDGroup, Version: CRDVersion} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: CRDGroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/apis/user/v1alpha1/zz_membership_terraformed.go b/apis/user/v1alpha1/zz_membership_terraformed.go new file mode 100755 index 0000000..a9eea26 --- /dev/null +++ b/apis/user/v1alpha1/zz_membership_terraformed.go @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +/* +Copyright 2022 Upbound Inc. +*/ + +// Code generated by upjet. DO NOT EDIT. + +package v1alpha1 + +import ( + "dario.cat/mergo" + "github.com/pkg/errors" + + "github.com/crossplane/upjet/pkg/resource" + "github.com/crossplane/upjet/pkg/resource/json" +) + +// GetTerraformResourceType returns Terraform resource type for this Membership +func (mg *Membership) GetTerraformResourceType() string { + return "github_membership" +} + +// GetConnectionDetailsMapping for this Membership +func (tr *Membership) GetConnectionDetailsMapping() map[string]string { + return nil +} + +// GetObservation of this Membership +func (tr *Membership) GetObservation() (map[string]any, error) { + o, err := json.TFParser.Marshal(tr.Status.AtProvider) + if err != nil { + return nil, err + } + base := map[string]any{} + return base, json.TFParser.Unmarshal(o, &base) +} + +// SetObservation for this Membership +func (tr *Membership) SetObservation(obs map[string]any) error { + p, err := json.TFParser.Marshal(obs) + if err != nil { + return err + } + return json.TFParser.Unmarshal(p, &tr.Status.AtProvider) +} + +// GetID returns ID of underlying Terraform resource of this Membership +func (tr *Membership) GetID() string { + if tr.Status.AtProvider.ID == nil { + return "" + } + return *tr.Status.AtProvider.ID +} + +// GetParameters of this Membership +func (tr *Membership) GetParameters() (map[string]any, error) { + p, err := json.TFParser.Marshal(tr.Spec.ForProvider) + if err != nil { + return nil, err + } + base := map[string]any{} + return base, json.TFParser.Unmarshal(p, &base) +} + +// SetParameters for this Membership +func (tr *Membership) SetParameters(params map[string]any) error { + p, err := json.TFParser.Marshal(params) + if err != nil { + return err + } + return json.TFParser.Unmarshal(p, &tr.Spec.ForProvider) +} + +// GetInitParameters of this Membership +func (tr *Membership) GetInitParameters() (map[string]any, error) { + p, err := json.TFParser.Marshal(tr.Spec.InitProvider) + if err != nil { + return nil, err + } + base := map[string]any{} + return base, json.TFParser.Unmarshal(p, &base) +} + +// GetInitParameters of this Membership +func (tr *Membership) GetMergedParameters(shouldMergeInitProvider bool) (map[string]any, error) { + params, err := tr.GetParameters() + if err != nil { + return nil, errors.Wrapf(err, "cannot get parameters for resource '%q'", tr.GetName()) + } + if !shouldMergeInitProvider { + return params, nil + } + + initParams, err := tr.GetInitParameters() + if err != nil { + return nil, errors.Wrapf(err, "cannot get init parameters for resource '%q'", tr.GetName()) + } + + // Note(lsviben): mergo.WithSliceDeepCopy is needed to merge the + // slices from the initProvider to forProvider. As it also sets + // overwrite to true, we need to set it back to false, we don't + // want to overwrite the forProvider fields with the initProvider + // fields. + err = mergo.Merge(¶ms, initParams, mergo.WithSliceDeepCopy, func(c *mergo.Config) { + c.Overwrite = false + }) + if err != nil { + return nil, errors.Wrapf(err, "cannot merge spec.initProvider and spec.forProvider parameters for resource '%q'", tr.GetName()) + } + + return params, nil +} + +// LateInitialize this Membership using its observed tfState. +// returns True if there are any spec changes for the resource. +func (tr *Membership) LateInitialize(attrs []byte) (bool, error) { + params := &MembershipParameters{} + if err := json.TFParser.Unmarshal(attrs, params); err != nil { + return false, errors.Wrap(err, "failed to unmarshal Terraform state parameters for late-initialization") + } + opts := []resource.GenericLateInitializerOption{resource.WithZeroValueJSONOmitEmptyFilter(resource.CNameWildcard)} + + li := resource.NewGenericLateInitializer(opts...) + return li.LateInitialize(&tr.Spec.ForProvider, params) +} + +// GetTerraformSchemaVersion returns the associated Terraform schema version +func (tr *Membership) GetTerraformSchemaVersion() int { + return 0 +} diff --git a/apis/user/v1alpha1/zz_membership_types.go b/apis/user/v1alpha1/zz_membership_types.go new file mode 100755 index 0000000..9bbb8ae --- /dev/null +++ b/apis/user/v1alpha1/zz_membership_types.go @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +/* +Copyright 2022 Upbound Inc. +*/ + +// Code generated by upjet. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" +) + +type MembershipInitParameters struct { + + // Defaults to false. If set to true, + // when this resource is destroyed, the member will not be removed + // from the organization. Instead, the member's role will be + // downgraded to 'member'. + // Instead of removing the member from the org, you can choose to downgrade their membership to 'member' when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization + DowngradeOnDestroy *bool `json:"downgradeOnDestroy,omitempty" tf:"downgrade_on_destroy,omitempty"` + + // The role of the user within the organization. + // Must be one of member or admin. Defaults to member. + // admin role represents the owner role available via GitHub UI. + // The role of the user within the organization. Must be one of 'member' or 'admin'. + Role *string `json:"role,omitempty" tf:"role,omitempty"` + + // The user to add to the organization. + // The user to add to the organization. + Username *string `json:"username,omitempty" tf:"username,omitempty"` +} + +type MembershipObservation struct { + + // Defaults to false. If set to true, + // when this resource is destroyed, the member will not be removed + // from the organization. Instead, the member's role will be + // downgraded to 'member'. + // Instead of removing the member from the org, you can choose to downgrade their membership to 'member' when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization + DowngradeOnDestroy *bool `json:"downgradeOnDestroy,omitempty" tf:"downgrade_on_destroy,omitempty"` + + Etag *string `json:"etag,omitempty" tf:"etag,omitempty"` + + ID *string `json:"id,omitempty" tf:"id,omitempty"` + + // The role of the user within the organization. + // Must be one of member or admin. Defaults to member. + // admin role represents the owner role available via GitHub UI. + // The role of the user within the organization. Must be one of 'member' or 'admin'. + Role *string `json:"role,omitempty" tf:"role,omitempty"` + + // The user to add to the organization. + // The user to add to the organization. + Username *string `json:"username,omitempty" tf:"username,omitempty"` +} + +type MembershipParameters struct { + + // Defaults to false. If set to true, + // when this resource is destroyed, the member will not be removed + // from the organization. Instead, the member's role will be + // downgraded to 'member'. + // Instead of removing the member from the org, you can choose to downgrade their membership to 'member' when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization + // +kubebuilder:validation:Optional + DowngradeOnDestroy *bool `json:"downgradeOnDestroy,omitempty" tf:"downgrade_on_destroy,omitempty"` + + // The role of the user within the organization. + // Must be one of member or admin. Defaults to member. + // admin role represents the owner role available via GitHub UI. + // The role of the user within the organization. Must be one of 'member' or 'admin'. + // +kubebuilder:validation:Optional + Role *string `json:"role,omitempty" tf:"role,omitempty"` + + // The user to add to the organization. + // The user to add to the organization. + // +kubebuilder:validation:Optional + Username *string `json:"username,omitempty" tf:"username,omitempty"` +} + +// MembershipSpec defines the desired state of Membership +type MembershipSpec struct { + v1.ResourceSpec `json:",inline"` + ForProvider MembershipParameters `json:"forProvider"` + // THIS IS A BETA FIELD. It will be honored + // unless the Management Policies feature flag is disabled. + // InitProvider holds the same fields as ForProvider, with the exception + // of Identifier and other resource reference fields. The fields that are + // in InitProvider are merged into ForProvider when the resource is created. + // The same fields are also added to the terraform ignore_changes hook, to + // avoid updating them after creation. This is useful for fields that are + // required on creation, but we do not desire to update them after creation, + // for example because of an external controller is managing them, like an + // autoscaler. + InitProvider MembershipInitParameters `json:"initProvider,omitempty"` +} + +// MembershipStatus defines the observed state of Membership. +type MembershipStatus struct { + v1.ResourceStatus `json:",inline"` + AtProvider MembershipObservation `json:"atProvider,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:storageversion + +// Membership is the Schema for the Memberships API. Provides a GitHub membership resource. +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="EXTERNAL-NAME",type="string",JSONPath=".metadata.annotations.crossplane\\.io/external-name" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,github} +type Membership struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + // +kubebuilder:validation:XValidation:rule="!('*' in self.managementPolicies || 'Create' in self.managementPolicies || 'Update' in self.managementPolicies) || has(self.forProvider.username) || (has(self.initProvider) && has(self.initProvider.username))",message="spec.forProvider.username is a required parameter" + Spec MembershipSpec `json:"spec"` + Status MembershipStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// MembershipList contains a list of Memberships +type MembershipList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Membership `json:"items"` +} + +// Repository type metadata. +var ( + Membership_Kind = "Membership" + Membership_GroupKind = schema.GroupKind{Group: CRDGroup, Kind: Membership_Kind}.String() + Membership_KindAPIVersion = Membership_Kind + "." + CRDGroupVersion.String() + Membership_GroupVersionKind = CRDGroupVersion.WithKind(Membership_Kind) +) + +func init() { + SchemeBuilder.Register(&Membership{}, &MembershipList{}) +} diff --git a/apis/zz_register.go b/apis/zz_register.go index ee3b808..bb16599 100755 --- a/apis/zz_register.go +++ b/apis/zz_register.go @@ -18,6 +18,7 @@ import ( v1alpha1enterprise "github.com/coopnorge/provider-github/apis/enterprise/v1alpha1" v1alpha1repo "github.com/coopnorge/provider-github/apis/repo/v1alpha1" v1alpha1team "github.com/coopnorge/provider-github/apis/team/v1alpha1" + v1alpha1user "github.com/coopnorge/provider-github/apis/user/v1alpha1" v1alpha1apis "github.com/coopnorge/provider-github/apis/v1alpha1" v1beta1 "github.com/coopnorge/provider-github/apis/v1beta1" ) @@ -29,6 +30,7 @@ func init() { v1alpha1enterprise.SchemeBuilder.AddToScheme, v1alpha1repo.SchemeBuilder.AddToScheme, v1alpha1team.SchemeBuilder.AddToScheme, + v1alpha1user.SchemeBuilder.AddToScheme, v1alpha1apis.SchemeBuilder.AddToScheme, v1beta1.SchemeBuilder.AddToScheme, ) diff --git a/config/external_name.go b/config/external_name.go index bded299..4df08ad 100644 --- a/config/external_name.go +++ b/config/external_name.go @@ -37,6 +37,8 @@ var ExternalNameConfigs = map[string]config.ExternalName{ // Imported by using the following format: {{ team_id }}:{{ username }} or {{ team_name }}:{{ username }} "github_team_membership": config.IdentifierFromProvider, // This cannot be imported. + "github_membership": config.IdentifierFromProvider, + // Can be imported using the following format: {{ organization}}:{{username }}. "github_actions_secret": config.IdentifierFromProvider, "github_actions_variable": config.IdentifierFromProvider, "github_enterprise_organization": config.IdentifierFromProvider, diff --git a/config/membership/config.go b/config/membership/config.go new file mode 100644 index 0000000..acddd18 --- /dev/null +++ b/config/membership/config.go @@ -0,0 +1,12 @@ +package membership + +import "github.com/crossplane/upjet/pkg/config" + +// Configure github_membership resource +func Configure(p *config.Provider) { + p.AddResourceConfigurator("github_membership", func(r *config.Resource) { + r.Kind = "Membership" + r.ShortGroup = "user" + + }) +} diff --git a/config/provider.go b/config/provider.go index 51df2cd..893051e 100644 --- a/config/provider.go +++ b/config/provider.go @@ -15,6 +15,7 @@ import ( "github.com/coopnorge/provider-github/config/defaultbranch" "github.com/coopnorge/provider-github/config/deploykey" "github.com/coopnorge/provider-github/config/emugroupmapping" + "github.com/coopnorge/provider-github/config/membership" "github.com/coopnorge/provider-github/config/organization" "github.com/coopnorge/provider-github/config/pullrequest" "github.com/coopnorge/provider-github/config/repository" @@ -63,6 +64,7 @@ func GetProvider() *ujconfig.Provider { actionssecret.Configure, actionsvariable.Configure, organization.Configure, + membership.Configure, } { configure(pc) } diff --git a/examples-generated/user/v1alpha1/membership.yaml b/examples-generated/user/v1alpha1/membership.yaml new file mode 100644 index 0000000..863f7d3 --- /dev/null +++ b/examples-generated/user/v1alpha1/membership.yaml @@ -0,0 +1,12 @@ +apiVersion: user.github.upbound.io/v1alpha1 +kind: Membership +metadata: + annotations: + meta.upbound.io/example-id: user/v1alpha1/membership + labels: + testing.upbound.io/example-name: membership_for_some_user + name: membership-for-some-user +spec: + forProvider: + role: member + username: SomeUser diff --git a/internal/controller/user/membership/zz_controller.go b/internal/controller/user/membership/zz_controller.go new file mode 100755 index 0000000..5b6eeb3 --- /dev/null +++ b/internal/controller/user/membership/zz_controller.go @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2023 The Crossplane Authors +// +// SPDX-License-Identifier: Apache-2.0 + +/* +Copyright 2022 Upbound Inc. +*/ + +// Code generated by upjet. DO NOT EDIT. + +package membership + +import ( + "time" + + "github.com/crossplane/crossplane-runtime/pkg/connection" + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + tjcontroller "github.com/crossplane/upjet/pkg/controller" + "github.com/crossplane/upjet/pkg/controller/handler" + "github.com/crossplane/upjet/pkg/terraform" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + + v1alpha1 "github.com/coopnorge/provider-github/apis/user/v1alpha1" + features "github.com/coopnorge/provider-github/internal/features" +) + +// Setup adds a controller that reconciles Membership managed resources. +func Setup(mgr ctrl.Manager, o tjcontroller.Options) error { + name := managed.ControllerName(v1alpha1.Membership_GroupVersionKind.String()) + var initializers managed.InitializerChain + cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())} + if o.SecretStoreConfigGVK != nil { + cps = append(cps, connection.NewDetailsManager(mgr.GetClient(), *o.SecretStoreConfigGVK, connection.WithTLSConfig(o.ESSOptions.TLSConfig))) + } + eventHandler := handler.NewEventHandler(handler.WithLogger(o.Logger.WithValues("gvk", v1alpha1.Membership_GroupVersionKind))) + ac := tjcontroller.NewAPICallbacks(mgr, xpresource.ManagedKind(v1alpha1.Membership_GroupVersionKind), tjcontroller.WithEventHandler(eventHandler)) + opts := []managed.ReconcilerOption{ + managed.WithExternalConnecter(tjcontroller.NewConnector(mgr.GetClient(), o.WorkspaceStore, o.SetupFn, o.Provider.Resources["github_membership"], tjcontroller.WithLogger(o.Logger), tjcontroller.WithConnectorEventHandler(eventHandler), + tjcontroller.WithCallbackProvider(ac), + )), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + managed.WithFinalizer(terraform.NewWorkspaceFinalizer(o.WorkspaceStore, xpresource.NewAPIFinalizer(mgr.GetClient(), managed.FinalizerName))), + managed.WithTimeout(3 * time.Minute), + managed.WithInitializers(initializers), + managed.WithConnectionPublishers(cps...), + managed.WithPollInterval(o.PollInterval), + } + if o.PollJitter != 0 { + opts = append(opts, managed.WithPollJitterHook(o.PollJitter)) + } + if o.Features.Enabled(features.EnableBetaManagementPolicies) { + opts = append(opts, managed.WithManagementPolicies()) + } + + // register webhooks for the kind v1alpha1.Membership + // if they're enabled. + if o.StartWebhooks { + if err := ctrl.NewWebhookManagedBy(mgr). + For(&v1alpha1.Membership{}). + Complete(); err != nil { + return errors.Wrap(err, "cannot register webhook for the kind v1alpha1.Membership") + } + } + + r := managed.NewReconciler(mgr, xpresource.ManagedKind(v1alpha1.Membership_GroupVersionKind), opts...) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(o.ForControllerRuntime()). + WithEventFilter(xpresource.DesiredStateChanged()). + Watches(&v1alpha1.Membership{}, eventHandler). + Complete(ratelimiter.NewReconciler(name, r, o.GlobalRateLimiter)) +} diff --git a/internal/controller/zz_setup.go b/internal/controller/zz_setup.go index 8b2aa06..244782f 100755 --- a/internal/controller/zz_setup.go +++ b/internal/controller/zz_setup.go @@ -24,6 +24,7 @@ import ( team "github.com/coopnorge/provider-github/internal/controller/team/team" teammembership "github.com/coopnorge/provider-github/internal/controller/team/teammembership" teamrepository "github.com/coopnorge/provider-github/internal/controller/team/teamrepository" + membership "github.com/coopnorge/provider-github/internal/controller/user/membership" ) // Setup creates all controllers with the supplied logger and adds them to @@ -45,6 +46,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { team.Setup, teammembership.Setup, teamrepository.Setup, + membership.Setup, } { if err := setup(mgr, o); err != nil { return err diff --git a/package/crds/user.github.upbound.io_memberships.yaml b/package/crds/user.github.upbound.io_memberships.yaml new file mode 100644 index 0000000..4e5d359 --- /dev/null +++ b/package/crds/user.github.upbound.io_memberships.yaml @@ -0,0 +1,379 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: memberships.user.github.upbound.io +spec: + group: user.github.upbound.io + names: + categories: + - crossplane + - managed + - github + kind: Membership + listKind: MembershipList + plural: memberships + singular: membership + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .metadata.annotations.crossplane\.io/external-name + name: EXTERNAL-NAME + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Membership is the Schema for the Memberships API. Provides a + GitHub membership resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: MembershipSpec defines the desired state of Membership + properties: + deletionPolicy: + default: Delete + description: |- + DeletionPolicy specifies what will happen to the underlying external + when this managed resource is deleted - either "Delete" or "Orphan" the + external resource. + This field is planned to be deprecated in favor of the ManagementPolicies + field in a future release. Currently, both could be set independently and + non-default values would be honored if the feature flag is enabled. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + enum: + - Orphan + - Delete + type: string + forProvider: + properties: + downgradeOnDestroy: + description: |- + Defaults to false. If set to true, + when this resource is destroyed, the member will not be removed + from the organization. Instead, the member's role will be + downgraded to 'member'. + Instead of removing the member from the org, you can choose to downgrade their membership to 'member' when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization + type: boolean + role: + description: |- + The role of the user within the organization. + Must be one of member or admin. Defaults to member. + admin role represents the owner role available via GitHub UI. + The role of the user within the organization. Must be one of 'member' or 'admin'. + type: string + username: + description: |- + The user to add to the organization. + The user to add to the organization. + type: string + type: object + initProvider: + description: |- + THIS IS A BETA FIELD. It will be honored + unless the Management Policies feature flag is disabled. + InitProvider holds the same fields as ForProvider, with the exception + of Identifier and other resource reference fields. The fields that are + in InitProvider are merged into ForProvider when the resource is created. + The same fields are also added to the terraform ignore_changes hook, to + avoid updating them after creation. This is useful for fields that are + required on creation, but we do not desire to update them after creation, + for example because of an external controller is managing them, like an + autoscaler. + properties: + downgradeOnDestroy: + description: |- + Defaults to false. If set to true, + when this resource is destroyed, the member will not be removed + from the organization. Instead, the member's role will be + downgraded to 'member'. + Instead of removing the member from the org, you can choose to downgrade their membership to 'member' when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization + type: boolean + role: + description: |- + The role of the user within the organization. + Must be one of member or admin. Defaults to member. + admin role represents the owner role available via GitHub UI. + The role of the user within the organization. Must be one of 'member' or 'admin'. + type: string + username: + description: |- + The user to add to the organization. + The user to add to the organization. + type: string + type: object + managementPolicies: + default: + - '*' + description: |- + THIS IS A BETA FIELD. It is on by default but can be opted out + through a Crossplane feature flag. + ManagementPolicies specify the array of actions Crossplane is allowed to + take on the managed and external resources. + This field is planned to replace the DeletionPolicy field in a future + release. Currently, both could be set independently and non-default + values would be honored if the feature flag is enabled. If both are + custom, the DeletionPolicy field will be ignored. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md + items: + description: |- + A ManagementAction represents an action that the Crossplane controllers + can take on an external resource. + enum: + - Observe + - Create + - Update + - Delete + - LateInitialize + - '*' + type: string + type: array + providerConfigRef: + default: + name: default + description: |- + ProviderConfigReference specifies how the provider that will be used to + create, observe, update, and delete this managed resource should be + configured. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + publishConnectionDetailsTo: + description: |- + PublishConnectionDetailsTo specifies the connection secret config which + contains a name, metadata and a reference to secret store config to + which any connection details for this managed resource should be written. + Connection details frequently include the endpoint, username, + and password required to connect to the managed resource. + properties: + configRef: + default: + name: default + description: |- + SecretStoreConfigRef specifies which secret store config should be used + for this ConnectionSecret. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + metadata: + description: Metadata is the metadata for connection secret. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations are the annotations to be added to connection secret. + - For Kubernetes secrets, this will be used as "metadata.annotations". + - It is up to Secret Store implementation for others store types. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels are the labels/tags to be added to connection secret. + - For Kubernetes secrets, this will be used as "metadata.labels". + - It is up to Secret Store implementation for others store types. + type: object + type: + description: |- + Type is the SecretType for the connection secret. + - Only valid for Kubernetes Secret Stores. + type: string + type: object + name: + description: Name is the name of the connection secret. + type: string + required: + - name + type: object + writeConnectionSecretToRef: + description: |- + WriteConnectionSecretToReference specifies the namespace and name of a + Secret to which any connection details for this managed resource should + be written. Connection details frequently include the endpoint, username, + and password required to connect to the managed resource. + This field is planned to be replaced in a future release in favor of + PublishConnectionDetailsTo. Currently, both could be set independently + and connection details would be published to both without affecting + each other. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - forProvider + type: object + x-kubernetes-validations: + - message: spec.forProvider.username is a required parameter + rule: '!(''*'' in self.managementPolicies || ''Create'' in self.managementPolicies + || ''Update'' in self.managementPolicies) || has(self.forProvider.username) + || (has(self.initProvider) && has(self.initProvider.username))' + status: + description: MembershipStatus defines the observed state of Membership. + properties: + atProvider: + properties: + downgradeOnDestroy: + description: |- + Defaults to false. If set to true, + when this resource is destroyed, the member will not be removed + from the organization. Instead, the member's role will be + downgraded to 'member'. + Instead of removing the member from the org, you can choose to downgrade their membership to 'member' when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization + type: boolean + etag: + type: string + id: + type: string + role: + description: |- + The role of the user within the organization. + Must be one of member or admin. Defaults to member. + admin role represents the owner role available via GitHub UI. + The role of the user within the organization. Must be one of 'member' or 'admin'. + type: string + username: + description: |- + The user to add to the organization. + The user to add to the organization. + type: string + type: object + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/tests/cases/membership/membership.yaml b/tests/cases/membership/membership.yaml new file mode 100644 index 0000000..cd5c862 --- /dev/null +++ b/tests/cases/membership/membership.yaml @@ -0,0 +1,12 @@ +apiVersion: user.github.upbound.io/v1alpha1 +kind: Membership +metadata: + annotations: + meta.upbound.io/example-id: user/v1alpha1/membership + labels: + testing.upbound.io/example-name: test-user + name: membership-for-test-user +spec: + forProvider: + role: member + username: TestUser From e0457c5ed04f6505a24ad628ab88b2b9edafa7ec Mon Sep 17 00:00:00 2001 From: Sachin Date: Mon, 11 Mar 2024 12:46:04 +0000 Subject: [PATCH 2/2] feat: Add github membership --- .../team/v1alpha1/teammembership.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/examples-generated/team/v1alpha1/teammembership.yaml b/examples-generated/team/v1alpha1/teammembership.yaml index dece233..023d93c 100644 --- a/examples-generated/team/v1alpha1/teammembership.yaml +++ b/examples-generated/team/v1alpha1/teammembership.yaml @@ -16,6 +16,21 @@ spec: --- +apiVersion: user.github.upbound.io/v1alpha1 +kind: Membership +metadata: + annotations: + meta.upbound.io/example-id: team/v1alpha1/teammembership + labels: + testing.upbound.io/example-name: membership_for_some_user + name: membership-for-some-user +spec: + forProvider: + role: member + username: SomeUser + +--- + apiVersion: team.github.upbound.io/v1alpha1 kind: Team metadata: