Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Does this work with compute endpoint? #106

Closed
praveenperera opened this issue Nov 16, 2023 · 16 comments · Fixed by #108
Closed

Does this work with compute endpoint? #106

praveenperera opened this issue Nov 16, 2023 · 16 comments · Fixed by #108
Labels
enhancement New feature or request

Comments

@praveenperera
Copy link

praveenperera commented Nov 16, 2023

I'm trying to get this to work with the compute endpoint

        let cloud_resource_prefix = format!("projects/{}", "my-project-staging");

        let client: GoogleApi<InstanceGroupsClient<GoogleAuthMiddleware>> =
            GoogleApi::from_function(
                InstanceGroupsClient::new,
                "https://compute.googleapis.com/",                
                Some(cloud_resource_prefix),
            )
            .await?;

    pub async fn list_instances(&self) -> Result<()> {
        let request = tonic::Request::new(AggregatedListInstanceGroupsRequest {
            filter: None,
            ..Default::default()
        });

        let req = AggregatedListInstanceGroupsRequest {
            filter: None,
            project: self.project.clone(),
            ..Default::default()
        }
        .into_request();

        println!("req: {:?}", req);

        let response = client.get().aggregated_list(request).await?;

        println!("{:?}", response);

        Ok(())

But I get

   0: status: Internal, message: "protocol error: received message with invalid compression flag: 60 (valid flags are 0 and 1) while receiving response with status: 404 Not Found", details: [], metadata: MetadataMap { headers: {"content-type": "text/html; charset=UTF-8", "referrer-policy": "no-referrer",
"content-length": "1614", "date": "Thu, 16 Nov 2023 03:41:49 GMT", "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"} }
@abdolence
Copy link
Owner

Hey, some of the Google APIs are not publicly available through gRPC.
In this case it is only REST: https://cloud.google.com/compute/docs/reference/rest/v1

So, if you want to work with it you need to use REST API. I don't think it is included in this project yet, so PR is welcome:
https://github.com/abdolence/gcloud-sdk-rs#how-apimodels-are-generated

@abdolence
Copy link
Owner

@praveenperera
Copy link
Author

praveenperera commented Nov 16, 2023

@abdolence I attempted to generate but get this error for the CLI

io.swagger.v3.parser.util.DeserializationUtils$SnakeException: Exception safe-checking yaml content  (maxDepth 2000, maxYamlAliasesForCollections 2147483647)
        at io.swagger.v3.parser.util.DeserializationUtils$CustomSnakeYamlConstructor.getSingleData(DeserializationUtils.java:483)
        at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:493)
        at org.yaml.snakeyaml.Yaml.load(Yaml.java:422)
        at io.swagger.v3.parser.util.DeserializationUtils.readYamlTree(DeserializationUtils.java:258)
        at io.swagger.v3.parser.util.DeserializationUtils.deserializeIntoTree(DeserializationUtils.java:172)
        at io.swagger.v3.parser.OpenAPIV3Parser.readContents(OpenAPIV3Parser.java:168)
        at io.swagger.v3.parser.OpenAPIV3Parser.readLocation(OpenAPIV3Parser.java:97)
        at io.swagger.parser.OpenAPIParser.readLocation(OpenAPIParser.java:16)
        at org.openapitools.codegen.config.CodegenConfigurator.toContext(CodegenConfigurator.java:653)
        at org.openapitools.codegen.config.CodegenConfigurator.toClientOptInput(CodegenConfigurator.java:711)
        at org.openapitools.codegen.cmd.Generate.execute(Generate.java:511)
        at org.openapitools.codegen.cmd.OpenApiGeneratorCommand.run(OpenApiGeneratorCommand.java:32)
        at org.openapitools.codegen.OpenAPIGenerator.main(OpenAPIGenerator.java:66)
        
[main] ERROR i.s.v.p.util.DeserializationUtils - Error parsing content
com.fasterxml.jackson.dataformat.yaml.JacksonYAMLParseException: The incoming YAML document exceeds the limit: 3145728 code points.
 at [Source: (StringReader); line: 59253, column: 41]
        at com.fasterxml.jackson.dataformat.yaml.YAMLParser.nextToken(YAMLParser.java:435)
        at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer._deserializeContainerNoRecursion(JsonNodeDeserializer.java:609)
        at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:100)
        at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:25)
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
        at com.fasterxml.jackson.databind.ObjectMapper._readTreeAndClose(ObjectMapper.java:4867)
        at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:3219)
        at io.swagger.v3.parser.util.DeserializationUtils.readYamlTree(DeserializationUtils.java:279)
        at io.swagger.v3.parser.util.DeserializationUtils.deserializeIntoTree(DeserializationUtils.java:172)
        at io.swagger.v3.parser.OpenAPIV3Parser.readContents(OpenAPIV3Parser.java:168)
        at io.swagger.v3.parser.OpenAPIV3Parser.readLocation(OpenAPIV3Parser.java:97)
        at io.swagger.parser.OpenAPIParser.readLocation(OpenAPIParser.java:16)
        at org.openapitools.codegen.config.CodegenConfigurator.toContext(CodegenConfigurator.java:653)
        at org.openapitools.codegen.config.CodegenConfigurator.toClientOptInput(CodegenConfigurator.java:711)
        at org.openapitools.codegen.cmd.Generate.execute(Generate.java:511)
        at org.openapitools.codegen.cmd.OpenApiGeneratorCommand.run(OpenApiGeneratorCommand.java:32)
        at org.openapitools.codegen.OpenAPIGenerator.main(OpenAPIGenerator.java:66)
        
Caused by: org.yaml.snakeyaml.error.YAMLException: The incoming YAML document exceeds the limit: 3145728 code points.
        at org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens(ScannerImpl.java:317)
        at org.yaml.snakeyaml.scanner.ScannerImpl.checkToken(ScannerImpl.java:238)
        at org.yaml.snakeyaml.parser.ParserImpl$ParseBlockSequenceEntryKey.produce(ParserImpl.java:537)
        at org.yaml.snakeyaml.parser.ParserImpl.peekEvent(ParserImpl.java:161)
        at org.yaml.snakeyaml.parser.ParserImpl.getEvent(ParserImpl.java:170)
        at com.fasterxml.jackson.dataformat.yaml.YAMLParser.nextToken(YAMLParser.java:429)        

@abdolence
Copy link
Owner

I'll have a look if this is solvable or the error is in open-api generator. (Of course fallback option is to use only REST client with Google Authentication and build models manually/externally - so it will be just like Reqwest + Google Authentication without models and interface generated).

@praveenperera
Copy link
Author

I was also thinking if the grpc request/response structs also had serde support I could use that with a pretty simple request client.

@abdolence
Copy link
Owner

I don't think prost does that and they also have some references to additional gRPC types from Google (such as timestamps)

@praveenperera
Copy link
Author

praveenperera commented Nov 16, 2023

Could the prost-serde help?

This is my current solution, I was able to get serde structs from another crate google_compute1 they dont have structs for requests tho so that's the only thing I have to do myself, which isn't a big issue.

use eyre::Result;
use gcp_auth::{AuthenticationManager, Token};
use google_compute1::api::InstanceGroupAggregatedList;
use reqwest::Client;

pub struct GoogleEnvironment {
    pub project_id: String,
}

impl GoogleEnvironment {
    pub async fn try_new() -> Result<Self> {
        let for_env = std::env::var("GCP_PROJECT")
            .ok()
            .or_else(|| std::env::var("PROJECT_ID").ok())
            .or_else(|| std::env::var("GCP_PROJECT_ID").ok());

        if let Some(project_id) = for_env {
            return Ok(Self { project_id });
        }

        if !google_cloud_metadata::on_gce().await {
            return Err(eyre::eyre!(
                "Not on GCE and GPC_PROJECT | PROJECT_ID | GCP_PROJECT_ID not set"
            ));
        }

        let project_id = google_cloud_metadata::project_id().await;

        Ok(Self { project_id })
    }
}

pub struct Auth {
    pub scopes: &'static [&'static str],
    pub env: GoogleEnvironment,
    pub manager: AuthenticationManager,
}

impl Auth {
    pub async fn try_new() -> Result<Self> {
        let manager = AuthenticationManager::new().await?;
        let scopes = &["https://www.googleapis.com/auth/cloud-platform"];
        let env = GoogleEnvironment::try_new().await?;

        Ok(Self {
            scopes,
            env,
            manager,
        })
    }

    pub async fn get_token(&self) -> Result<Token> {
        let token = self.manager.get_token(self.scopes).await?;
        Ok(token)
    }
}

pub struct ComputeEngine {
    auth: Auth,
    client: Client,
    base_url: String,
}

impl ComputeEngine {
    pub async fn try_new() -> Result<Self> {
        let auth = Auth::try_new().await?;

        let project = &auth.env.project_id;
        let base_url = format!("https://compute.googleapis.com/compute/v1/projects/{project}");

        Ok(Self {
            client: Client::new(),
            auth,
            base_url,
        })
    }

    pub async fn list_gpu_instances(&self) -> Result<()> {
        let url = format!("{}/aggregated/instanceGroups", self.base_url,);
        let token = self.auth.get_token().await?;

        let resp = self
            .client
            .get(&url)
            .query(&[("filter", r#"name eq ".*gpu*""#)])
            .bearer_auth(token.as_str())
            .send()
            .await?;

        let instances: InstanceGroupAggregatedList = resp.json().await?;
        // let instances = resp.text().await?;

        println!("{:#?}", instances);

        Ok(())
    }
}

@abdolence
Copy link
Owner

Ok, I managed to fix Open API generator upgrading it to the next version and providing additional parameter.

let google_project_id = gcloud_sdk::GoogleEnvironment::detect_google_project_id().await
        .expect("No Google Project ID detected. Please specify it explicitly using env variable: PROJECT_ID");

    let google_rest_client = gcloud_sdk::GoogleRestApi::new().await.unwrap();

    let response = gcloud_sdk::google_rest_apis::compute_v1::instances_api::compute_instances_list(
        &google_rest_client.create_google_compute_v1_config().await.unwrap(),
        gcloud_sdk::google_rest_apis::compute_v1::instances_api::ComputePeriodInstancesPeriodListParams {
            project: google_project_id.to_string(),
            zone: "us-central1-a".to_string(),
            dollar_xgafv: None,
            access_token: None,
            alt: None,
            callback: None,
            fields: None,
            key: None,
            oauth_token: None,
            pretty_print: None,
            quota_user: None,
            upload_protocol: None,
            upload_type: None,
            user_ip: None,
            filter: None,
            max_results: None,
            order_by: None,
            page_token: None,
            return_partial_success: None,
        }
    ).await.unwrap();

Unfortunately in the new version of Open API generator, they removed Default trait from all structs. Details here: OpenAPITools/openapi-generator#10845
but other than that it works.

Help with the testing would be appreciated (you can refer to a git feature branch in your deps directly)
#108

@abdolence abdolence added the enhancement New feature or request label Nov 16, 2023
@abdolence abdolence linked a pull request Nov 16, 2023 that will close this issue
@praveenperera
Copy link
Author

Thats great! Let me try it out now. I'll let you know how it goes.

@praveenperera
Copy link
Author

Tested it works, thanks!

@abdolence
Copy link
Owner

Let me contemplate a bit about what to do with Defaults, and going to release a bit later this week. Thanks for helping out with the testing.

@abdolence
Copy link
Owner

@praveenperera
Copy link
Author

@abdolence thanks! This is working well my only problem is it adds a lot of compile time. Would it be possible to add more features to only select the parts of the compute API that I want?

@abdolence
Copy link
Owner

@praveenperera the compute engine's spec is huge (~3 Mb), and it generated a lot of stuff as I can see. The problem with the feature toggles is that this crate already has around 400+ features, and I had to ask the maintainers of crates.io even to whitelist this crate, since they have started limiting crates having more than 300 features recently (https://blog.rust-lang.org/2023/10/26/broken-badges-and-23k-keywords.html).

If it is unbearable it needs some kind of workarounds, like extracting CE into separate crate with its own features and using just REST client.

@praveenperera
Copy link
Author

Ya it's huge not sure what to do about it.

On a release build on my M1 Studio (Apple M1 Max) It adds about 30 seconds to the build time.

@abdolence
Copy link
Owner

I usually run release builds on pipelines. They often requires a lot of time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants