Add/delete members to M365 groups using MS Graph API with Golang - go

I'm trying to add and/or delete group members using graph/api with Golang
I am able to list members, or owners for groups, but not able to add to delete members.
//---CODE--- To list member of groups this code works an intended
cred, err := azidentity.NewClientSecretCredential("7c...","16...","sj...", nil,)
auth, err := a.NewAzureIdentityAuthenticationProviderWithScopes(cred, []string{"https://graph.microsoft.com/.default"})
requestAdapter, err := msgraphsdk.NewGraphRequestAdapter(auth)
graphClient := msgraphsdk.NewGraphServiceClient(requestAdapter)
result, err := graphClient.Users().Get()
// get member in 1 group
graphClientGroup := msgraphsdk.NewGraphServiceClient(requestAdapter)
r2, err := graphClientGroup.GroupsById("...group-id...").Members().Get()
When I try any variation for adding including as listed in MS docs:
https://learn.microsoft.com/en-us/graph/api/group-post-members?view=graph-rest-1.0&tabs=go
requestBody := msgraphsdk.New()
requestBody.SetAdditionalData(map[string]interface{}{
"#odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/{id}",
}
graphClient.GroupsById(&groupId).MembersById(&directoryObjectId).Post(requestBody)
This code will not compile, other variations will run but not produce new group members or return an error.
For deleting members in M365 groups I tried to run code without success such as:
graphClient.GroupsById("groupId...").MembersById("memberID...").Ref().Delete()
No errors are returned and NO member deleted from group?
Thank you, for any guidance.

As mentioned in the documentation in GitHub:
The Microsoft Graph Go SDK is currently in Community Preview and breaking changes are expecting to happen to the SDK based on community's feedback.
The error with post() has been already reported. Details here

This issue is now fixed, as of 2022-05-31 v0.25.0

Related

How do I access custom fields in an error?

Objective
Add a command to dropbox's CLI tool to get the shared link for the given path (file or folder).
The changes are here: github fork.
Background
The dropbox-go-sdk has a function that takes a path, and returns a new shared link, or returns an error containing the existing shared link.
I don't know how to use the error to extract the existing shared link.
Code
on github, and snippet here:
dbx := sharing.New(config)
res, err := dbx.CreateSharedLinkWithSettings(arg)
if err != nil {
switch e := err.(type) {
case sharing.CreateSharedLinkWithSettingsAPIError:
fmt.Printf("%v", e.EndpointError)
default:
return err
}
}
This prints the following:
&{{shared_link_already_exists} <nil> <nil>}found unknown shared link typeError: shared_link_already_exists/...
tracing:
CreateSharedLinkWithSettings --> CreateSharedLinkWithSettingsAPIError --> CreateSharedLinkWithSettingsError --> SharedLinkAlreadyExistsMetadata --> IsSharedLinkMetadata
IsSharedLinkMetadata contains the Url that I'm looking for.
More Info
The API docs point to CreateSharedLinkWithSettings, which should pass back the information in the error including the existing Url.
I struggle to understand how to deal with the error and extract the url from it.
The dbxcli has some code doing a similar operation, but again, not sure how it's working enough to apply it to the code I'm working on. Is it a Struct? Map? I don't know what this thing is called. There's some weird magic err.(type) stuff happening in the code. How do I access the data?
dbx := sharing.New(config)
res, err := dbx.CreateSharedLinkWithSettings(arg)
if err != nil {
switch e := err.(type) {
case sharing.CreateSharedLinkWithSettingsAPIError:
fmt.Printf("%v", e.EndpointError)
// type cast to the specific error and access the field you want.
settingsError := err.(sharing.CreateSharedLinkWithSettingsAPIError)
fmt.Println(settingsError.EndpointError.SharedLinkAlreadyExists.Metadata.Url)
default:
return err
}
}
The question was answered in the comments by #jimb. The answer is you access the fields like any other golang data structure - nothing special.
The errors I got when trying to access the fields were because the fields were not there.
The problem with the code was dependency issues. The code depends on an older version of the go-sdk and I referenced the latest version.
This question serves as a good explanation for how real golang programmers handle errors in their code with examples. I wasn't able to find this online, so I won't close the question.

How to get a list of all files in directory with Google Drive API(v3)

I have stuck with function that must to return me a list of all files from directory (in this case directory is "root"). When I call this function, it return me files that only I added with my program (this program also can upload files to Google Drive), not all files. And it also shows me files that I deleted :/. What I do wrong?
This function I was copied from Google Drive API Quickstart
service, err := getService()
if err != nil {
log.Fatalf("Unable to retrieve Drive client: %v", err)
}
r, err := service.Files.List().Q("'root' in parents").Do()
if err != nil {
log.Fatalf("Unable to retrieve files: %v", err)
}
fmt.Println("Files:")
if len(r.Files) == 0 {
fmt.Println("No files found.")
} else {
for _, i := range r.Files {
fmt.Printf("%v (%vs )\n", i.Name, i.Id)
}
}
You want to retrieve all files just under the root folder.
You want to achieve this using google-api-go-client with golang.
You have already been get and put values for Google Drive using Drive API.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Issue and workaround:
From the situation of When I call this function, it return me files that only I added with my program (this program also can upload files to Google Drive), not all files., I thought that your scopes might include https://www.googleapis.com/auth/drive.file. When https://www.googleapis.com/auth/drive.file is used as the scope, only the files created by the application are retrieved.
In order to retrieve all files just under the root folder, please use the following scopes.
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/drive.readonly
https://www.googleapis.com/auth/drive.metadata.readonly
https://www.googleapis.com/auth/drive.metadata.
If you want to retrieve only the file list, the scopes of .readonly can be used.
Modified script:
From your question, I could notice that you are using google-api-go-client with golang and Go Quickstart. In this case, how about the following modification?
If drive.DriveFileScope is included in the scopes, please modify as follows.
From:
config, err := google.ConfigFromJSON(b, drive.DriveFileScope)
To:
config, err := google.ConfigFromJSON(b, drive.DriveMetadataScope)
or
config, err := google.ConfigFromJSON(b, drive.DriveReadonlyScope)
If you want to also upload the file, please use drive.DriveScope.
Note:
When you modified the scopes, please remove the file of token.json of tokFile := "token.json". And please run the script and authorize again. By this, the modified scopes are reflected to the access token and refresh token. Please be careful this.
References:
google-api-go-client
Go Quickstart
Files: list
If I misunderstood your question and this was not the direction you want, I apologize.

Using client-go to `kubectl apply` against the Kubernetes API directly with multiple types in a single YAML file

I'm using https://github.com/kubernetes/client-go and all works well.
I have a manifest (YAML) for the official Kubernetes Dashboard: https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml
I want to mimic kubectl apply of this manifest in Go code, using client-go.
I understand that I need to do some (un)marshalling of the YAML bytes into the correct API types defined in package: https://github.com/kubernetes/api
I have successfully Createed single API types to my cluster, but how do I do this for a manifest that contains a list of types that are not the same? Is there a resource kind: List* that supports these different types?
My current workaround is to split the YAML file using csplit with --- as the delimiter
csplit /path/to/recommended.yaml /---/ '{*}' --prefix='dashboard.' --suffix-format='%03d.yaml'
Next, I loop over the new (14) parts that were created, read their bytes, switch on the type of the object returned by the UniversalDeserializer's decoder and call the correct API methods using my k8s clientset.
I would like to do this to programmatically to make updates to any new versions of the dashboard into my cluster. I will also need to do this for the Metrics Server and many other resources. The alternative (maybe simpler) method is to ship my code with kubectl installed to the container image and directly call kubectl apply -f -; but that means I also need to write the kube config to disk or maybe pass it inline so that kubectl can use it.
I found this issue to be helpful: https://github.com/kubernetes/client-go/issues/193
The decoder lives here: https://github.com/kubernetes/apimachinery/tree/master/pkg/runtime/serializer
It's exposed in client-go here: https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L69
I've also taken a look at the RunConvert method that is used by kubectl: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L139 and assume that I can provide my own genericclioptions.IOStreams to get the output?
It looks like RunConvert is on a deprecation path
I've also looked at other questions tagged [client-go] but most use old examples or use a YAML file with a single kind defined, and the API has changed since.
Edit: Because I need to do this for more than one cluster and am creating clusters programmatically (AWS EKS API + CloudFormation/eksctl), I would like to minimize the overhead of creating ServiceAccounts across many cluster contexts, across many AWS accounts. Ideally, the only authentication step involved in creating my clientset is using aws-iam-authenticator to get a token using cluster data (name, region, CA cert, etc). There hasn't been a release of aws-iam-authenticator for a while, but the contents of master allow for the use of a third-party role cross-account role and external ID to be passed. IMO, this is cleaner than using a ServiceAccount (and IRSA) because there are other AWS services the application (the backend API which creates and applies add-ons to these clusters) needs to interact with.
Edit: I have recently found https://github.com/ericchiang/k8s. It's definitely simpler to use than client-go, at a high-level, but doesn't support this behavior.
It sounds like you've figured out how to deserialize YAML files into Kubernetes runtime.Objects, but the problem is dynamically deploying a runtime.Object without writing special code for each Kind.
kubectl achieves this by interacting with the REST API directly. Specifically, via resource.Helper.
In my code, I have something like:
import (
meta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/apimachinery/pkg/runtime"
)
func createObject(kubeClientset kubernetes.Interface, restConfig rest.Config, obj runtime.Object) error {
// Create a REST mapper that tracks information about the available resources in the cluster.
groupResources, err := restmapper.GetAPIGroupResources(kubeClientset.Discovery())
if err != nil {
return err
}
rm := restmapper.NewDiscoveryRESTMapper(groupResources)
// Get some metadata needed to make the REST request.
gvk := obj.GetObjectKind().GroupVersionKind()
gk := schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}
mapping, err := rm.RESTMapping(gk, gvk.Version)
if err != nil {
return err
}
name, err := meta.NewAccessor().Name(obj)
if err != nil {
return err
}
// Create a client specifically for creating the object.
restClient, err := newRestClient(restConfig, mapping.GroupVersionKind.GroupVersion())
if err != nil {
return err
}
// Use the REST helper to create the object in the "default" namespace.
restHelper := resource.NewHelper(restClient, mapping)
return restHelper.Create("default", false, obj, &metav1.CreateOptions{})
}
func newRestClient(restConfig rest.Config, gv schema.GroupVersion) (rest.Interface, error) {
restConfig.ContentConfig = resource.UnstructuredPlusDefaultContentConfig()
restConfig.GroupVersion = &gv
if len(gv.Group) == 0 {
restConfig.APIPath = "/api"
} else {
restConfig.APIPath = "/apis"
}
return rest.RESTClientFor(&restConfig)
}
I was able to get this working in one of my projects. I had to use much of the source code from kubectl's apply command to get it working correctly.
https://github.com/billiford/go-clouddriver/blob/master/pkg/kubernetes/client.go#L63

How to add filter in computeService.Zones.List(project) Google Cloud Platform API

I am trying to filter the zone list, in the Google Cloud Platform API
But I am not able to find any documentation in Google saying to put filter in the API:
req := computeService.Zones.List(project)
Above line of code will list the Zone in the Google Cloud Compute
in command line we can do the same
gcloud compute zones list --filter="name:us-"
Thanks,
Sid
It will help someone in case:
req := computeService.Zones.List("ProjectName")
if err := req.Filter("name=us*").Pages(ctx, func(page *compute.ZoneList) error {
//Your code
}

How to get all Modifying Users of a specific Revision

Follow up on Google Drive Rest API : How to get all Modifying Users of a specific Revision
It has been approximately 3 years, so I am not sure what the status is, but I thought I would ask again on the status.
I see that Google Drive API # https://developers.google.com/drive/api/v3/reference/revisions/get
should actually do exactly what has been asked here, but when I make a call to the API it returns null for LastModifyingUser
I am not sure if this is a work in progress API or I am doing something wrong, so any help would be appreciated.
Just to provide some reference, I am posting some basic code that is an addition to what can be found here... https://developers.google.com/drive/api/v3/quickstart/go
revision, err := srv.Revisions.Get(fileId, revisionId).Do() //fieldId and revisionId are fatched using proper calls
if err != nil {
log.Fatalf("Unable to retrieve revision: %v", err)
}
fmt.Println("Revision:")
fmt.Printf("%+v\n", revision.LastModifyingUser)
You want to retrieve the value of lastModifyingUser from Revisions.Get() using Drive API v3.
If my understanding is correct, how about adding the fields? At the default, the fields are id,mimeType,modifiedTime. So when you want to retrieve only values of lastModifyingUser, please modify as follows.
From:
revision, err := srv.Revisions.Get(fileId, revisionId).Do()
To:
revision, err := srv.Revisions.Get(fileID, revisionID).Fields("lastModifyingUser").Do()
Note:
In this modified script, it is supposes that when you run your current script, no error occurs.
If you want to add lastModifyingUser to the default values of id,mimeType,modifiedTime, please set the fields to id,mimeType,modifiedTime,lastModifyingUser.
Reference:
Revisions
If I misunderstand your question, I'm sorry.

Resources