Skip to content

Commit

Permalink
Feat: Add codefresh_idp and codefresh_account_idp resource and codefr…
Browse files Browse the repository at this point in the history
…esh_account_idp datasource (#138)

## What
Add codefresh_idp and codefresh_account_idp resource and
codefresh_account_idp datasource
## Why
New resources for identity providers
## Notes
<!-- Add any notes here -->

## Checklist

* [ ] _I have read
[CONTRIBUTING.md](https://github.com/codefresh-io/terraform-provider-codefresh/blob/master/CONTRIBUTING.md)._
* [ ] _I have [allowed changes to my fork to be
made](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)._
* [ ] _I have added tests, assuming new tests are warranted_.
* [ ] _I understand that the `/test` comment will be ignored by the CI
trigger [unless it is made by a repo admin or
collaborator](https://codefresh.io/docs/docs/pipelines/triggers/git-triggers/#support-for-building-pull-requests-from-forks)._

---------

Co-authored-by: Yonatan Koren <[email protected]>
  • Loading branch information
ilia-medvedev-codefresh and korenyoni authored Feb 28, 2024
1 parent c2313c5 commit 44cd0e2
Show file tree
Hide file tree
Showing 17 changed files with 2,549 additions and 39 deletions.
233 changes: 207 additions & 26 deletions codefresh/cfclient/idp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,200 @@ package cfclient
import (
"errors"
"fmt"
"log"
"net/url"
)

type IDP struct {
Access_token string `json:"access_token,omitempty"`
Accounts []string `json:"accounts,omitempty"`
ApiHost string `json:"apiHost,omitempty"`
ApiPathPrefix string `json:"apiPathPrefix,omitempty"`
ApiURL string `json:"apiURL,omitempty"`
AppId string `json:"appId,omitempty"`
AuthURL string `json:"authURL,omitempty"`
ClientHost string `json:"clientHost,omitempty"`
ClientId string `json:"clientId,omitempty"`
ClientName string `json:"clientName,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
ClientType string `json:"clientType,omitempty"`
CookieIv string `json:"cookieIv,omitempty"`
CookieKey string `json:"cookieKey,omitempty"`
DisplayName string `json:"displayName,omitempty"`
ID string `json:"_id,omitempty"`
IDPLoginUrl string `json:"IDPLoginUrl,omitempty"`
LoginUrl string `json:"loginUrl,omitempty"`
RedirectUiUrl string `json:"redirectUiUrl,omitempty"`
RedirectUrl string `json:"redirectUrl,omitempty"`
RefreshTokenURL string `json:"refreshTokenURL,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Tenant string `json:"tenant,omitempty"`
TokenSecret string `json:"tokenSecret,omitempty"`
TokenURL string `json:"tokenURL,omitempty"`
UserProfileURL string `json:"userProfileURL,omitempty"`
ID string `json:"_id,omitempty"`
Access_token string `json:"access_token,omitempty"`
Accounts []string `json:"accounts,omitempty"`
ClientName string `json:"clientName,omitempty"` // IDP name
ClientType string `json:"clientType,omitempty"` // IDP type
DisplayName string `json:"displayName,omitempty"`
LoginUrl string `json:"loginUrl,omitempty"` // Login url in Codefresh
RedirectUiUrl string `json:"redirectUiUrl,omitempty"` // Redicrect url Codefresh UI
RedirectUrl string `json:"redirectUrl,omitempty"`
ClientId string `json:"clientId,omitempty"` // All providers (base)
ClientSecret string `json:"clientSecret,omitempty"` // All providers (base)
ApiHost string `json:"apiHost,omitempty"` // GitHub
ApiPathPrefix string `json:"apiPathPrefix,omitempty"` // Github
// Bitbucket, Gitlab
ApiURL string `json:"apiURL,omitempty"`
// Azure, Okta, onelogin,saml
AppId string `json:"appId,omitempty"`
// Github, Gitlab
AuthURL string `json:"authURL,omitempty"`
// saml, okta, onelogin, auth0, azure, google, google-cloud-sr
ClientHost string `json:"clientHost,omitempty"`
// Azure
CookieIv string `json:"cookieIv,omitempty"`
// Azure
CookieKey string `json:"cookieKey,omitempty"`
// Azure
IDPLoginUrl string `json:"IDPLoginUrl,omitempty"`
// Bitbucket
RefreshTokenURL string `json:"refreshTokenURL,omitempty"`
// Multiple - computed
Scopes []string `json:"scopes,omitempty"`
// Azure
Tenant string `json:"tenant,omitempty"`
TokenSecret string `json:"tokenSecret,omitempty"`
// Okta, Bitbucket, GitHub, Keycloak
TokenURL string `json:"tokenURL,omitempty"`
// Github, Gitlab
UserProfileURL string `json:"userProfileURL,omitempty"`
// Okta
SyncMirrorAccounts []string `json:"syncMirrorAccounts,omitempty"`
// Google, Ldap
AllowedGroupsForSync string `json:"allowedGroupsForSync,omitempty"`
// Google
Subject string `json:"subject,omitempty"`
// Google
KeyFile string `json:"keyfile,omitempty"`
// Google
SyncField string `json:"syncField,omitempty"`
// Azure
AutoGroupSync bool `json:"autoGroupSync,omitempty"`
// Google,Okta,saml
ActivateUserAfterSync bool `json:"activateUserAfterSync,omitempty"`
// Azure
SyncInterval string `json:"syncInterval,omitempty"`
// Onelogin
ApiClientId string `json:"apiClientId,omitempty"`
// Onelogin
ApiClientSecret string `json:"apiClientSecret,omitempty"`
// Keycloak
Host string `json:"host,omitempty"`
// keycloak
Realm string `json:"realm,omitempty"`
// SAML
EntryPoint string `json:"entryPoint,omitempty"`
// SAML
ApplicationCert string `json:"cert,omitempty"`
// SAML
SamlProvider string `json:"provider,omitempty"`
// ldap
Password string `json:"password,omitempty"`
Url string `json:"url,omitempty"`
DistinguishedName string `json:"distinguishedName,omitempty"`
SearchBase string `json:"searchBase,omitempty"`
SearchFilter string `json:"searchFilter,omitempty"`
SearchBaseForSync string `json:"searchBaseForSync,omitempty"`
Certificate string `json:"certificate,omitempty"`
}

// Return the appropriate API endpoint for platform and account scoped IDPs
func getAPIEndpoint(isGlobal bool) string {
// If IDP is platform scoped
if isGlobal {
return "/admin/idp"
} else {
return "/idp/account"
}
}

// Currently on create the API sometimes (like when creating saml idps) returns a different structure for accounts than on read making the client crash on decode
// For now we are disabling response decode and in the resource will instead call the read function again
func (client *Client) CreateIDP(idp *IDP, isGlobal bool) (id string, err error) {

body, err := EncodeToJSON(idp)

if err != nil {
return "", err
}
opts := RequestOptions{
Path: getAPIEndpoint(isGlobal),
Method: "POST",
Body: body,
}

resp, err := client.RequestAPI(&opts)

if err != nil {
log.Printf("[DEBUG] Call to API for IDP creation failed with Error = %v for Body %v", err, body)
return "", err
}

var respIDP map[string]interface{}
err = DecodeResponseInto(resp, &respIDP)

if err != nil {
return "", nil
}

return respIDP["id"].(string), nil
}

// Currently on update the API returns a different structure for accounts than on read making the client crash on decode
// For now we are disabling response decode and in the resource will instead call the read function again
func (client *Client) UpdateIDP(idp *IDP, isGlobal bool) error {

body, err := EncodeToJSON(idp)

if err != nil {
return err
}
opts := RequestOptions{
Path: getAPIEndpoint(isGlobal),
Method: "PUT",
Body: body,
}

_, err = client.RequestAPI(&opts)

if err != nil {
log.Printf("[DEBUG] Call to API for IDP update failed with Error = %v for Body %v", err, body)
return err
}

// var respIDP IDP
// err = DecodeResponseInto(resp, &respIDP)
// if err != nil {
// return nil, err
// }

return nil
}

func (client *Client) DeleteIDP(id string) error {
baseUrl := getAPIEndpoint(true)
fullPath := fmt.Sprintf("%s/%s", baseUrl, url.PathEscape(id))
opts := RequestOptions{
Path: fullPath,
Method: "DELETE",
}

_, err := client.RequestAPI(&opts)

if err != nil {
return err
}

return nil
}

func (client *Client) DeleteIDPAccount(id string) error {

body, err := EncodeToJSON(map[string]interface{}{"id": id})

if err != nil {
return err
}

opts := RequestOptions{
Path: getAPIEndpoint(false),
Method: "DELETE",
Body: body,
}

_, err = client.RequestAPI(&opts)

if err != nil {
return err
}

return nil
}

// get all idps
Expand Down Expand Up @@ -115,6 +280,22 @@ func (client *Client) GetAccountIDPs() (*[]IDP, error) {
return &idps, nil
}

func (client *Client) GetAccountIdpByID(idpID string) (*IDP, error) {

idpList, err := client.GetAccountIDPs()
if err != nil {
return nil, err
}

for _, idp := range *idpList {
if idp.ID == idpID {
return &idp, nil
}
}

return nil, errors.New(fmt.Sprintf("[ERROR] IDP with ID %s isn't found.", idpID))
}

// add account to idp
func (client *Client) AddAccountToIDP(accountId, idpId string) error {

Expand Down
101 changes: 101 additions & 0 deletions codefresh/data_account_idp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package codefresh

import (
"fmt"

"github.com/codefresh-io/terraform-provider-codefresh/codefresh/cfclient"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceAccountIdp() *schema.Resource {
return &schema.Resource{
Description: "This data source retrieves an account level identity provider",
Read: dataSourceAccountIdpRead,
Schema: AccountIdpSchema(),
}
}

// IdpSchema -
func AccountIdpSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"_id": {
Type: schema.TypeString,
Optional: true,
ExactlyOneOf: []string{"_id", "client_name"},
},
"client_name": {
Type: schema.TypeString,
Optional: true,
ExactlyOneOf: []string{"_id", "client_name"},
},
"display_name": {
Type: schema.TypeString,
Computed: true,
},
"client_type": {
Type: schema.TypeString,
Computed: true,
},
"redirect_url": {
Description: "API Callback url for the identity provider",
Type: schema.TypeString,
Computed: true,
},
"redirect_ui_url": {
Description: "UI Callback url for the identity provider",
Type: schema.TypeString,
Computed: true,
},
"login_url": {
Description: "Login url using the IDP to Codefresh",
Type: schema.TypeString,
Computed: true,
},
}
}

func dataSourceAccountIdpRead(d *schema.ResourceData, meta interface{}) error {

client := meta.(*cfclient.Client)

idps, err := client.GetAccountIDPs()
if err != nil {
return err
}

_id, _idOk := d.GetOk("_id")
clientName, clientNameOk := d.GetOk("client_name")

for _, idp := range *idps {
if clientNameOk && clientName.(string) != idp.ClientName {
continue
}
if _idOk && _id.(string) != idp.ID {
continue
}

err = mapDataAccountIdpToResource(idp, d)
if err != nil {
return err
}
}

if d.Id() == "" {
return fmt.Errorf("[EROOR] Idp wasn't found")
}

return nil
}

func mapDataAccountIdpToResource(cfClientIDP cfclient.IDP, d *schema.ResourceData) error {

d.SetId(cfClientIDP.ID)
d.Set("client_name", cfClientIDP.ClientName)
d.Set("client_type", cfClientIDP.ClientType)
d.Set("display_name", cfClientIDP.DisplayName)
d.Set("redirect_url", cfClientIDP.RedirectUrl)
d.Set("redirect_ui_url", cfClientIDP.RedirectUiUrl)
d.Set("login_url", cfClientIDP.LoginUrl)

return nil
}
2 changes: 2 additions & 0 deletions codefresh/internal/idp/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package idp is shared by idp-related resources.
package idp
Loading

0 comments on commit 44cd0e2

Please sign in to comment.