package registrar import ( "context" "fmt" "time" "github.com/shurcooL/graphql" edgeClient "edge-infra.dev/pkg/edge/api/client" "edge-infra.dev/pkg/edge/api/graph/model" ) // Allows to register things with a (D-SDS) cluster, e.g. terminals, network services etc. type Registrar struct { Client *edgeClient.EdgeClient } func (registrar *Registrar) GetBFFClient() *edgeClient.EdgeClient { return registrar.Client } func (registrar *Registrar) GetBanner(ctx context.Context, bannerName string, bffClient *edgeClient.EdgeClient) (Banner, error) { var query struct { Banners []Banner `graphql:"banners()"` } err := bffClient.Query(ctx, &query, map[string]interface{}{}) if err != nil { fmt.Println("an error occurred retrieving banner:", err.Error()) } banners := query.Banners for _, banner := range banners { if banner.Name == bannerName { fmt.Println("retrieved banner:", banner.BannerEdgeID) return banner, nil } } return Banner{}, fmt.Errorf("could not find banner: %s: %v", bannerName, err) } func (registrar *Registrar) GetCluster(ctx context.Context, storeName, bannerName string) (Cluster, error) { bffClient := registrar.GetBFFClient() banner, err := registrar.GetBanner(ctx, bannerName, bffClient) if err != nil { return Cluster{}, err } var query struct { Clusters []Cluster `graphql:"clusters(bannerEdgeId: $bannerEdgeId)"` } err = bffClient.Query(ctx, &query, map[string]interface{}{"bannerEdgeId": graphql.String(banner.BannerEdgeID)}) if err != nil { return Cluster{}, fmt.Errorf("fail to call bff graphql clusters: %v", err) } clusters := query.Clusters for _, cluster := range clusters { if cluster.ClusterName == storeName { fmt.Println("retrieved cluster:", cluster.ClusterEdgeID) return cluster, nil } } return Cluster{}, fmt.Errorf("cluster does not exist: %s", storeName) } func (registrar *Registrar) GetTerminal(ctx context.Context, terminalID string) (*model.Terminal, error) { bffClient := registrar.GetBFFClient() var query struct { model.Terminal `graphql:"terminal(terminalId: $terminalId)"` } variables := map[string]interface{}{ "terminalId": graphql.String(terminalID), } if err := bffClient.Query(ctx, &query, variables); err != nil { return nil, err } return &query.Terminal, nil } func (registrar *Registrar) RegisterTerminal( ctx context.Context, terminalInput model.TerminalCreateInput, ) (*model.Terminal, error) { // bffClient := registrar.GetBFFClient() var mutation struct { model.Terminal `graphql:"createTerminal(newTerminal: $terminalInput)"` } variables := map[string]interface{}{ "terminalInput": terminalInput, } err := bffClient.Mutate(ctx, &mutation, variables) if err != nil { return nil, fmt.Errorf("fail to call bff graphql createTerminal: %v", err) } return &mutation.Terminal, nil } func (registrar *Registrar) RegisterTerminals(terminals []*model.TerminalCreateInput) ([]*model.Terminal, error) { var terms []*model.Terminal for _, terminal := range terminals { // Despite the name, calling a CancelFunc after a _successful_ request is fine (it's a no-op). // Using defer is the best way to ensure it's called, but in a loop will not behave as expected. // Wrap it in a closure to ensure correct behaviour. term, err := func() (*model.Terminal, error) { reqCtx, cancelReq := context.WithTimeout(context.Background(), time.Duration(30)*time.Second) defer cancelReq() term, err := registrar.RegisterTerminal(reqCtx, *terminal) return term, err }() if err != nil { return nil, err } terms = append(terms, term) } return terms, nil } func (registrar *Registrar) DeleteTerminal(ctx context.Context, terminalID string) (bool, error) { bffClient := registrar.GetBFFClient() var mutation struct { Status graphql.Boolean `graphql:"deleteTerminal(terminalId: $terminalId)"` } variables := map[string]interface{}{ "terminalId": graphql.String(terminalID), } err := bffClient.Mutate(ctx, &mutation, variables) if err != nil { return false, fmt.Errorf("failed to call bff graphql deleteTerminal: %v", err) } return bool(mutation.Status), nil } func (registrar *Registrar) DeleteTerminalDisk(ctx context.Context, terminalDiskID string) (bool, error) { bffClient := registrar.GetBFFClient() var mutation struct { Status graphql.Boolean `graphql:"deleteTerminalDisk(terminalDiskId: $terminalDiskId)"` } variables := map[string]interface{}{ "terminalDiskId": graphql.String(terminalDiskID), } err := bffClient.Mutate(ctx, &mutation, variables) if err != nil { return false, fmt.Errorf("failed to call bff graphql deleteTerminalDisk: %v", err) } return bool(mutation.Status), nil } func (registrar *Registrar) ModifyTerminal(ctx context.Context, terminalID string, terminalValues *model.TerminalUpdateInput) (*model.Terminal, error) { bffClient := registrar.GetBFFClient() var mutation struct { NewTerminal model.Terminal `graphql:"updateTerminal(terminal: {terminalId: $terminalId, terminalValues: $terminalValues})"` } variables := map[string]interface{}{ "terminalId": graphql.String(terminalID), "terminalValues": *terminalValues, } if err := bffClient.Mutate(ctx, &mutation, variables); err != nil { return nil, err } return &mutation.NewTerminal, nil } func (registrar *Registrar) ModifyTerminalInterface(ctx context.Context, terminalInterfaceID string, updatedInterfaceInput *model.TerminalInterfaceUpdateInput) (*model.TerminalInterface, error) { bffClient := registrar.GetBFFClient() var mutation struct { UpdatedTerminalInterface model.TerminalInterface `graphql:"updateTerminalInterface(interface: {terminalInterfaceId: $terminalInterfaceId, terminalInterfaceValues: $terminalInterfaceValues})"` } variables := map[string]interface{}{ "terminalInterfaceId": graphql.String(terminalInterfaceID), "terminalInterfaceValues": *updatedInterfaceInput, } if err := bffClient.Mutate(ctx, &mutation, variables); err != nil { return nil, err } return &mutation.UpdatedTerminalInterface, nil } func (registrar *Registrar) ModifyTerminalAddress(ctx context.Context, terminalAddressID string, updatedAddressInput *model.TerminalAddressUpdateInput) (*model.TerminalAddress, error) { bffClient := registrar.GetBFFClient() var mutation struct { UpdatedTerminalAddress model.TerminalAddress `graphql:"updateTerminalAddress(address: {terminalAddressId: $terminalAddressId, terminalAddressValues: $terminalAddressValues})"` } variables := map[string]interface{}{ "terminalAddressId": graphql.String(terminalAddressID), "terminalAddressValues": *updatedAddressInput, } if err := bffClient.Mutate(ctx, &mutation, variables); err != nil { return nil, err } return &mutation.UpdatedTerminalAddress, nil } func (registrar *Registrar) ModifyTerminalDisk(ctx context.Context, terminalDiskID string, updateDiskInput *model.TerminalDiskUpdateInput) (*model.TerminalDisk, error) { bffClient := registrar.GetBFFClient() var mutation struct { UpdatedTerminalDisk model.TerminalDisk `graphql:"updateTerminalDisk(terminalDisk: {terminalDiskId: $terminalDiskId, terminalDiskValues: $terminalDiskValues})"` } variables := map[string]interface{}{ "terminalDiskId": graphql.String(terminalDiskID), "terminalDiskValues": *updateDiskInput, } if err := bffClient.Mutate(ctx, &mutation, variables); err != nil { return nil, err } return &mutation.UpdatedTerminalDisk, nil } func (registrar *Registrar) RegisterNetworkServices( ctx context.Context, clusterEdgeID string, networkServices []model.CreateNetworkServiceInfo, ) ([]model.ClusterNetworkServiceInfo, error) { // bffClient := registrar.GetBFFClient() var mutation struct { NetworkServices []model.ClusterNetworkServiceInfo `graphql:"createClusterNetworkServices(clusterEdgeId: $clusterEdgeId, networkServicesInfo: $networkServicesInfo)"` } variables := map[string]interface{}{ "clusterEdgeId": graphql.String(clusterEdgeID), "networkServicesInfo": networkServices, } err := bffClient.Mutate(ctx, &mutation, variables) if err != nil { return nil, fmt.Errorf("fail to call bff graphql createClusterNetworkServices: %v", err) } return mutation.NetworkServices, nil } func (registrar *Registrar) ModifyNetworkServices( ctx context.Context, clusterEdgeID string, networkServices []model.UpdateNetworkServiceInfo, ) ([]model.ClusterNetworkServiceInfo, error) { // bffClient := registrar.GetBFFClient() var mutation struct { NetworkServices []model.ClusterNetworkServiceInfo `graphql:"updateClusterNetworkServices(clusterEdgeId: $clusterEdgeId, networkServicesInfo: $networkServicesInfo)"` } variables := map[string]interface{}{ "clusterEdgeId": graphql.String(clusterEdgeID), "networkServicesInfo": networkServices, } err := bffClient.Mutate(ctx, &mutation, variables) if err != nil { return nil, fmt.Errorf("fail to call bff graphql updateClusterNetworkServices: %v", err) } return mutation.NetworkServices, nil } func (registrar *Registrar) DeleteNetworkServices( ctx context.Context, clusterEdgeID string, networkServiceID string, ) (bool, error) { // bffClient := registrar.GetBFFClient() var mutation struct { Status graphql.Boolean `graphql:"deleteClusterNetworkService(clusterEdgeId: $clusterEdgeId, networkServiceId: $networkServiceId)"` } variables := map[string]interface{}{ "clusterEdgeId": graphql.String(clusterEdgeID), "networkServiceId": graphql.String(networkServiceID), } err := bffClient.Mutate(ctx, &mutation, variables) if err != nil { return false, fmt.Errorf("fail to call bff graphql deleteClusterNetworkService: %v", err) } return bool(mutation.Status), nil } func (registrar *Registrar) UpdateClusterSecret(ctx context.Context, clusterEdgeID, secretType, secretValue string) error { clusterSecretType, err := validateSecretType(secretType) if err != nil { return err } bffClient := registrar.GetBFFClient() var mutation struct { Status graphql.Boolean `graphql:"updateClusterSecret(clusterEdgeId: $clusterEdgeId, secretType: $secretType, secretValue: $secretValue)"` } variables := map[string]interface{}{ "clusterEdgeId": graphql.String(clusterEdgeID), "secretType": clusterSecretType, "secretValue": graphql.String(secretValue), } err = bffClient.Mutate(ctx, &mutation, variables) if err != nil { return fmt.Errorf("fail to call bff graphql updateClusterSecret: %v", err) } return nil } func (registrar *Registrar) RefreshActivationCode(ctx context.Context, terminalID string) (string, error) { bffClient := registrar.GetBFFClient() var mutation struct { ActivationCode graphql.String `graphql:"refreshActivationCode(terminalId: $terminalId)"` } variables := map[string]interface{}{ "terminalId": graphql.String(terminalID), } if err := bffClient.Mutate(ctx, &mutation, variables); err != nil { return "", err } return string(mutation.ActivationCode), nil } func (registrar *Registrar) GetActivationCode(ctx context.Context, terminalID string) (string, error) { bffClient := registrar.GetBFFClient() var query struct { ActivationCode graphql.String `graphql:"activationCode(terminalId: $terminalId)"` } variables := map[string]interface{}{ "terminalId": graphql.String(terminalID), } if err := bffClient.Query(ctx, &query, variables); err != nil { return "", err } return string(query.ActivationCode), nil } func (registrar *Registrar) CreateClusterConfig(ctx context.Context, clusterEdgeID string, clusterConfig model.CreateClusterConfig) error { bffClient := registrar.GetBFFClient() var mutation struct { CreateClusterConfig model.ClusterConfig `graphql:"createClusterConfig(clusterEdgeId: $clusterEdgeId, createClusterConfig: $createClusterConfig)"` } variables := map[string]interface{}{ "clusterEdgeId": graphql.String(clusterEdgeID), "createClusterConfig": clusterConfig, } return bffClient.Mutate(ctx, &mutation, variables) } func (registrar *Registrar) GetTerminals(ctx context.Context, clusterEdgeID string) ([]*model.Terminal, error) { bffClient := registrar.GetBFFClient() var query struct { Terminals []*model.Terminal `graphql:"terminals(clusterEdgeId: $clusterEdgeId)"` } if err := bffClient.Query(ctx, &query, map[string]interface{}{"clusterEdgeId": graphql.String(clusterEdgeID)}); err != nil { return query.Terminals, fmt.Errorf("fail to call bff graphql terminals: %v", err) } return query.Terminals, nil } func (registrar *Registrar) UpdateClusterConfig(ctx context.Context, clusterEdgeID string, clusterConfig model.UpdateClusterConfig) error { bffClient := registrar.GetBFFClient() var mutation struct { UpdateClusterConfig model.ClusterConfig `graphql:"updateClusterConfig(clusterEdgeId: $clusterEdgeId, updateClusterConfig: $updateClusterConfig)"` } variables := map[string]interface{}{ "clusterEdgeId": graphql.String(clusterEdgeID), "updateClusterConfig": clusterConfig, } return bffClient.Mutate(ctx, &mutation, variables) } func (registrar *Registrar) GetClusterConfig(ctx context.Context, clusterEdgeID string) (*model.ClusterConfig, error) { bffClient := registrar.GetBFFClient() var query struct { ClusterConfig *model.ClusterConfig `graphql:"clusterConfig(clusterEdgeId: $clusterEdgeId)"` } variables := map[string]interface{}{ "clusterEdgeId": graphql.String(clusterEdgeID), } if err := bffClient.Query(ctx, &query, variables); err != nil { return nil, err } return query.ClusterConfig, nil } func (registrar *Registrar) GetClusterSecret(ctx context.Context, clusterEdgeID string, st string, version string) (string, error) { secretType, err := validateSecretType(st) if err != nil { return "", err } bffClient := registrar.GetBFFClient() var query struct { ClusterSecret string `graphql:"clusterSecret(clusterEdgeID: $clusterEdgeID, secretType: $secretType, version: $version)"` } if err := bffClient.Query(ctx, &query, map[string]interface{}{"clusterEdgeID": graphql.String(clusterEdgeID), "secretType": secretType, "version": graphql.String(version)}); err != nil { return query.ClusterSecret, fmt.Errorf("failed to call bff graphql clusterSecret: %v", err) } return query.ClusterSecret, nil } func (registrar *Registrar) GetClusterSecretVersions(ctx context.Context, clusterEdgeID string, st string) ([]*model.ClusterSecretVersionInfo, error) { secretType, err := validateSecretType(st) if err != nil { return []*model.ClusterSecretVersionInfo{}, err } bffClient := registrar.GetBFFClient() var query struct { ClusterSecretVersions []*model.ClusterSecretVersionInfo `graphql:"clusterSecretVersions(clusterEdgeID: $clusterEdgeID, secretType: $secretType)"` } if err := bffClient.Query(ctx, &query, map[string]interface{}{"clusterEdgeID": graphql.String(clusterEdgeID), "secretType": secretType}); err != nil { return query.ClusterSecretVersions, fmt.Errorf("failed to call bff graphql clusterSecret: %v", err) } return query.ClusterSecretVersions, nil } func (registrar *Registrar) GetClusterSecretLease(ctx context.Context, clusterEdgeID string, st string) (*model.ClusterSecretLease, error) { secretType, err := validateSecretType(st) if err != nil { return nil, err } bffClient := registrar.GetBFFClient() var query struct { ClusterSecretLease model.ClusterSecretLease `graphql:"clusterSecretLease(clusterEdgeID: $clusterEdgeID, secretType: $secretType)"` } if err := bffClient.Query(ctx, &query, map[string]interface{}{"clusterEdgeID": graphql.String(clusterEdgeID), "secretType": secretType}); err != nil { return nil, fmt.Errorf("failed to call bff graphql clusterSecretLease: %v", err) } return &query.ClusterSecretLease, nil } func (registrar *Registrar) RevokeClusterSecretLease(ctx context.Context, clusterEdgeID string, st string, username string) (bool, error) { secretType, err := validateSecretType(st) if err != nil { return false, err } bffClient := registrar.GetBFFClient() var mutation struct { RevokeClusterSecretLease bool `graphql:"revokeClusterSecretLease(clusterEdgeId: $clusterEdgeID, secretType: $secretType, username: $username)"` } if err := bffClient.Mutate(ctx, &mutation, map[string]interface{}{"clusterEdgeID": graphql.String(clusterEdgeID), "secretType": secretType, "username": graphql.String(username)}); err != nil { return mutation.RevokeClusterSecretLease, fmt.Errorf("failed to call bff graphql revokeClusterSecretLease: %v", err) } return mutation.RevokeClusterSecretLease, nil } func (registrar *Registrar) ReleaseClusterSecretLease(ctx context.Context, clusterEdgeID string, st string) (bool, error) { secretType, err := validateSecretType(st) if err != nil { return false, err } bffClient := registrar.GetBFFClient() var mutation struct { RevokeClusterSecretLease bool `graphql:"releaseClusterSecretLease(clusterEdgeId: $clusterEdgeID, secretType: $secretType)"` } if err := bffClient.Mutate(ctx, &mutation, map[string]interface{}{"clusterEdgeID": graphql.String(clusterEdgeID), "secretType": secretType}); err != nil { return mutation.RevokeClusterSecretLease, fmt.Errorf("failed to call bff graphql releaseClusterSecretLease: %v", err) } return mutation.RevokeClusterSecretLease, nil } func (registrar *Registrar) UpdateEdgeSecurityCompliance(ctx context.Context, bannerName string, optIn string) (*model.EdgeResponsePayload, error) { var edgeSecurityCompliance model.EdgeSecurityComplianceOptions bffClient := registrar.GetBFFClient() banner, err := registrar.GetBanner(ctx, bannerName, bffClient) if err != nil { return &model.EdgeResponsePayload{}, err } if optIn == "true" { edgeSecurityCompliance = "optIn" } else if optIn == "false" { edgeSecurityCompliance = "optOut" } else if optIn == "default" { edgeSecurityCompliance = "default" } else { return &model.EdgeResponsePayload{}, fmt.Errorf("invalid security compliance option. must choose from true, false or default") } var mutation struct { UpdateEdgeSecurityCompliance *model.EdgeResponsePayload `graphql:"updateBanner(bannerEdgeId: $bannerEdgeID, edgeSecurityCompliance: $securityCompliance)"` } if err := bffClient.Mutate(ctx, &mutation, map[string]interface{}{"bannerEdgeID": graphql.String(banner.BannerEdgeID), "securityCompliance": edgeSecurityCompliance}); err != nil { return mutation.UpdateEdgeSecurityCompliance, fmt.Errorf("failed to call bff graphql updateBanner: %v", err) } return mutation.UpdateEdgeSecurityCompliance, nil } func (registrar *Registrar) CreateTerminalDisk(ctx context.Context, terminalID string, newTerminalDisk model.TerminalDiskCreateInput) error { bffClient := registrar.GetBFFClient() var mutation struct { CreateTerminalDisk model.TerminalDisk `graphql:"createTerminalDisk(terminalId: $terminalId, newTerminalDisk: $newTerminalDisk)"` } variables := map[string]interface{}{ "terminalId": graphql.String(terminalID), "newTerminalDisk": newTerminalDisk, } return bffClient.Mutate(ctx, &mutation, variables) } func validateSecretType(secretType string) (model.ClusterSecretType, error) { switch secretType { case "breakglass": return model.ClusterSecretTypeBreakglass, nil case "grub": return model.ClusterSecretTypeGrub, nil default: return model.ClusterSecretTypeBreakglass, fmt.Errorf("invalid secret type %s", secretType) } } // Banner is the value type of GetBanner type Banner struct { // UUID of the banner in sql database BannerEdgeID string `graphql:"bannerEdgeId" json:"bannerEdgeId"` Name string `graphql:"name" json:"name"` } // Cluster is the value type of GetCluster type Cluster struct { // UUID of the cluster in sql database ClusterEdgeID string `graphql:"clusterEdgeId" json:"clusterEdgeId"` ClusterName string `graphql:"name" json:"clusterName"` ClusterNetworkServices []*model.ClusterNetworkServiceInfo `graphql:"clusterNetworkServices" json:"clusterNetworkServices"` }