Format gcloud compute instances list to get single metadata value - google-cloud-platform

As part of some GCP admin automation I'm trying to run a gcloud compute instances list command to return a few instance properties, one of which is a single metadata property. I cannot find in the documentation how to return only a single metadata property.
This is what I would think is correct based on the doc, but I don't get any metadata properties returned...
gcloud compute instances list --filter="name~^my-machine.*-type" --zones=zone1,zone2,zone3 --format="json(name,metadata.items.MY_VALUE)"
How can I return a single metadata value?

Eesh... this was not obvious ;-)
KEY=...
gcloud compute instances list \
--project=${PROJECT} \
--format="value(metadata.items.extract("${KEY}"))"
See extract
I'm not sure why it works.
In my case:
gcloud compute instances list \
--project=${PROJECT} \
--format="value(metadata.items)"
Yields:
{'key': 'gce-container-declaration', 'value': "..."};
{'key': 'google-logging-enabled', 'value': 'true'}
NOTE a semi-colon separated list of JSON objects
So, metadata.items appears to be a list of JSON objects {"key": $KEY, "value": $VALUE} and I think this why you can't walk down through the values using something ... metadata.items.key["google-logging-enabled"] or similar.
When I initially looked using YAML, this wasn't obvious and I think, even though the YAML looks flat, the items are embedded and the --format=yaml is doing something clever:
gcloud compute instances list \
--project=${PROJECT} \
--format="yaml(metadata.items)"
---
metadata:
items:
- key: gce-container-declaration
value: |-
...
- key: google-logging-enabled
value: 'true'
But:
gcloud compute instances list \
--project=${PROJECT} \
--format="value(metadata.items.extract("gce-container-declaration"))"
Yields:
spec:
containers:
- name: instance-1
image: ...
stdin: false
tty: false
restartPolicy: Always

Related

jsonpath for nested arrays in kubectl get

I am trying to get the resource limits & requests for Kubernetes pods. I am attempting to output to a comma delimited row that lists the namespace, pod name, container name and then the mem & CPU limits/requests for each container. Running into issues when there's multiple containers per pod.
The closest I've been able to get is this which will print out a single row for each pod. If there are multiple containers, they are listed in separate "columns" in the same row.
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{#.metadata.namespace}{","}{#.metadata.name}{","}{range .spec.containers[*]}{.name}{","}{#.resources.requests.cpu}{","}{#.resources.requests.memory}{","}{#.resources.limits.cpu}{","}{#.resources.limits.memory}{","}{end}{"\n"}{end}'
The output looks like this:
kube-system,metrics-server-5f8d84558d-g926z,metrics-server-vpa,5m,30Mi,100m,300Mi,metrics-server,46m,63Mi,46m,63Mi,
What I would like to see is something like this:
kube-system,metrics-server-5f8d84558d-g926z,metrics-server-vpa,5m,30Mi,100m,300Mi,
kube-system,metrics-server-5f8d84558d-g926z,metrics-server,46m,63Mi,46m,63Mi,
Appreciate any assistance. Thanks.
I think (don't know that) you can't using only kubectl's (limited) JSONPath.
There's a UNIX principle that each tool should do one thing well:
kubectl does Kubernetes stuff well and can output JSON
jq does JSON processing well.
If you're willing to use another tool:
FILTER='
.items[]
|.metadata as $meta
|.spec.containers[]
|.name as $name
|.resources.requests as $requests
|.resources.limits as $limits
|[
$meta.namespace,
$meta.name,$name,
$requests.cpu,
$requests.memory,
$limits.cpu,
$limits.memory
]
|#csv
'
kubectl get pods \
--all-namespaces \
--output=json \
| jq -r "${FILTER}"
Explanation:
For each items (i.e. each Pod)
Set the variable meta to the (Pod's) metadata content
For each containers (i.e. each Container)
Set the variable name as the (Container's) name
Set the variable requests as the (Container's Resources') requests
Set the variable limits as the (Container's Resources') limits
Create an array ([...]) by reassembling the relevant pieces
Output the arrays as comma-delimited
On a cluster:
"monitoring","prometheus-adapter-59df95d9f5-kg4hc","prometheus-adapter",,,,
"monitoring","prometheus-adapter-59df95d9f5-j6rbx","prometheus-adapter",,,,
"monitoring","prometheus-operator-7775c66ccf-45z2f","prometheus-operator","100m","100Mi","200m","200Mi"
"monitoring","prometheus-operator-7775c66ccf-45z2f","kube-rbac-proxy","10m","20Mi","20m","40Mi"
"monitoring","node-exporter-7cf4m","node-exporter","102m","180Mi","250m","180Mi"
"monitoring","node-exporter-7cf4m","kube-rbac-proxy","10m","20Mi","20m","40Mi"
"monitoring","kube-state-metrics-76f6cb7996-hdxcb","kube-state-metrics","10m","190Mi","100m","250Mi"
"monitoring","kube-state-metrics-76f6cb7996-hdxcb","kube-rbac-proxy-main","20m","20Mi","40m","40Mi"
"monitoring","kube-state-metrics-76f6cb7996-hdxcb","kube-rbac-proxy-self","10m","20Mi","20m","40Mi"
"monitoring","blackbox-exporter-55c457d5fb-x6hwj","blackbox-exporter","10m","20Mi","20m","40Mi"
"monitoring","blackbox-exporter-55c457d5fb-x6hwj","module-configmap-reloader","10m","20Mi","20m","40Mi"
"monitoring","blackbox-exporter-55c457d5fb-x6hwj","kube-rbac-proxy","10m","20Mi","20m","40Mi"
"monitoring","grafana-6dd5b5f65-6jwq8","grafana","100m","100Mi","200m","200Mi"
"monitoring","alertmanager-main-0","alertmanager","4m","100Mi","100m","100Mi"
"monitoring","alertmanager-main-0","config-reloader","100m","50Mi","100m","50Mi"
"kube-system","coredns-7f9c69c78c-2zx4h","coredns","100m","70Mi",,"170Mi"
"monitoring","prometheus-k8s-0","prometheus",,"400Mi",,
"monitoring","prometheus-k8s-0","config-reloader","100m","50Mi","100m","50Mi"
"kube-system","calico-kube-controllers-5f7575cc96-6tf8x","calico-kube-controllers",,,,
"kube-system","calico-node-m78xm","calico-node","250m",,,
Here is one way to obtain the output natively using kubectl via the go-template output format. jsonpath is not the right tool(maybe doable) for this requirement; perhaps piping to jq or go-template is the appropriate solution.
kubectl get pod -o go-template='{{- range $index, $element := .items -}}
{{- range $container, $status := $element.spec.containers -}}
{{- printf "%s,%s,%s,%s,%s,%s,%s\n" $element.metadata.namespace $element.metadata.name $status.name (or $status.resources.requests.cpu "" ) (or $status.resources.requests.memory "") (or $status.resources.limits.memory "") (or $status.resources.limits.cpu "") -}}
{{- end -}}
{{- end -}}'

How to get latest version of an image from artifact registry

is there a command (gcloud) that return the latest fully qualified name of an image from Artifact registry
Try:
PROJECT=
REGION=
REPO=
IMAGE=
gcloud artifacts docker images list \
${REGION}-docker.pkg.dev/${PROJECT}/${REPO} \
--filter="package=${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${IMAGE}" \
--sort-by="~UPDATE_TIME" \
--limit=1 \
--format="value(format("{0}#{1}",package,version))"
Because:
Filters the list for a specific image
Sorts the results descending (~) by UPDATE_TIME1
Only takes 1 value i.e. the most recent
Outputs the results as {package}#{version}
1 -- Curiously, --sort-by uses the output (!) field name not the underlying type (surfaced by e.g. --format=json or --format=yaml) name.
Many thanks to the previous answer, I use it to remove the tag "latest" of my last pushed artifact. I then add it when I push another. Leaving here if anyone interested.
Doc : https://cloud.google.com/artifact-registry/docs/docker/manage-images#tag
Remove tag :
gcloud artifacts docker tags delete \
$(gcloud artifacts docker images list ${REGION}-docker.pkg.dev/\
${PROJECT}/${REPO}/${IMAGE}/\
--filter="package=${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${IMAGE}"\
--sort-by="~UPDATE_TIME" --limit=1 --format="value(format("{0}",package))"):latest
Add tag:
gcloud artifacts docker tags add \
$(gcloud artifacts docker images list \
${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${IMAGE}/ \
--filter="package=${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${IMAGE}" \
--sort-by="~UPDATE_TIME" --limit=1 \
--format="value(format("{0}#{1}",package,version))") \
$(gcloud artifacts docker images list \
${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${IMAGE}/ \
--filter="package=${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${IMAGE}" \
--sort-by="~UPDATE_TIME" --limit=1 \
--format="value(format("{0}",package))"):latest

How to escape slash in gcloud format / filter command?

I would like to filter Cloud Run revisions by its container image.
When I run this gcloud run revisions command,
gcloud beta run revisions list --service sample-service --region=asia-northeast1 --limit=5 --sort-by="~DEPLOYED" --format="json"
it will output following json
[
{
"apiVersion": "serving.knative.dev/v1",
"kind": "Revision",
"metadata": {
"annotations": {
"autoscaling.knative.dev/maxScale": "1",
"client.knative.dev/user-image": "asia.gcr.io/sample-gcp-project/sample-app:e88597bcfb346aa1",
"run.googleapis.com/client-name": "gcloud",
"run.googleapis.com/client-version": "383.0.1", #
I tried to filter revisions by --filter options, but it raises an error.
gcloud beta run revisions list --service it-sys-watch --region=asia-northeast1 --limit=1 --sort-by="~DEPLOYED" --filter='metadata.annotations.client.knative.dev/user-image=asia.gcr.io/sample-gcp-project/sample-app:e88597bcfb346aa1'
ERROR: (gcloud.beta.run.revisions.list) Non-empty key name expected [metadata.annotations.client.knative.dev *HERE* /user-image=asia.gcr.io/sample-gcp-project/sample-app:e88597bcfb346aa1].
Neither adding backslash nor double slashes won't work
gcloud beta run revisions list --service it-sys-watch --region=asia-northeast1 --limit=1 --sort-by="~DEPLOYED" --filter='metadata.annotations.client.knative.dev\/user-image=asia.gcr.io/sample-gcp-project/sample-app:e88597bcfb346aa1'
WARNING: The following filter keys were not present in any resource : metadata.annotations.client.knative.dev\/user-image
Listed 0 items.
gcloud beta run revisions list --service it-sys-watch --region=asia-northeast1 --limit=1 --sort-by="~DEPLOYED" --filter='metadata.annotations.client.knative.dev//user-image=asia.gcr.io/sample-gcp-project/sample-app:e88597bcfb346aa1'
ERROR: (gcloud.beta.run.revisions.list) Non-empty key name expected [metadata.annotations.client.knative.dev *HERE* //user-image=asia.gcr.io/sample-gcp-project/sample-app:e88597bcfb346aa1].
gcloud --format options also does not work with backslash keys.
Is there any idea to help filtering key with slashes?
Try:
gcloud beta run revisions list \
--service=it-sys-watch \
--region=asia-northeast1 \
--sort-by="~DEPLOYED" \
--filter='metadata.annotations["client.knative.dev/user-image"]="asia.gcr.io/sample-gcp-project/sample-app:e88597bcfb346aa1"'
NOTE You need to drop the --limit=1 too though this conflicts with the documentation that suggests that limit is applied after filter
gcloud ... --filter=... --limit=1 | jq 'length' yields 0
gcloud ... --filter=... | jq 'length' yields 1
Let's see what Google Engineering says: 231192444

Can I replicate `create-with-container` gcloud sdk command using the googleapiclient python client

I currently have a docker container on the gcloud container repository which I want to launch an instance of in order to run a calculation, save a result and close:
gcloud compute instances create-with-container test-1 \
--machine-type=n1-standard-4 \
--boot-disk-size=20GB \
--container-image=eu.gcr.io/<container-link> \
--container-env=GCLOUD_INPUT_FILENAME=file.txt \
--container-env=GCLOUD_PROJECT=project-name
However, I want to be able to launch these instances using a web-interface (flask) which implies I want to use the googleapiclient (python) in order to create and manage these instances:
It looks like while you can create a instance creation order using the discovery api:
compute = googleclientapi.discovery.build('compute', 'v1')
compute.instances().insert(...).execute()
but it doesn't look like it is possible to emulate create-with-container gcloud sdk command, although you can pass 'machineImage' as part of the creation request.
Can one create a compute instance 'with-container' without using subprocess to call the gcloud sdk
OR
Can I convert my create-with-container instance into a machine image and then use the googleapi client?
Just for FYI, what I found from the log-http logging(I know it is not the right answer)
==== request start ====
uri: https://compute.googleapis.com/batch/compute/v1
method: POST
== headers start ==
b'authorization': --- Token Redacted ---
b'content-length': b'479'
b'content-type': b'multipart/mixed; boundary="===============34234234234234=="'
b'user-agent': b'google-cloud-sdk gcloud/329.0.0 command/gcloud.compute.instances.create-with-container invocation-id/0dd6a37ac0624ac4b00e30a44da environment/devshell environment-version/None interactive/False from-script/False python/3.7.3 term/screen (Linux 5.4.89+)'
== headers end ==
"create-with-container" is passed through the header, I did not find the body for the same.
The code would benefit from refinement but here's a solution:
import os
from google.cloud import compute_v1
from google.cloud.compute_v1 import types
project=os.getenv("PROJECT")
number=os.getenv("NUMBER")
zone=os.getenv("ZONE")
machine_type=os.getenv("TYPE")
name=os.getenv("NAME")
# Compute Engine default
email=f"{number}-compute#developer.gserviceaccount.com"
# Define a non-trivial container with command and args
value="""
spec:
containers:
- name: foo
image: gcr.io/google-containers/busybox#sha256:d8d3bc2c183ed2f9f10e7258f84971202325ee6011ba137112e01e30f206de67
command:
- /bin/sh
args:
- -c
- |
while true
do
echo "Hello"
sleep 15
done
stdin: true
tty: true
restartPolicy: Always
"""
client = compute_v1.InstancesClient()
request = types.InsertInstanceRequest(
project=project,
zone=zone,
instance_resource=types.Instance(
name=name,
machine_type=f"zones/{zone}/machineTypes/{machine_type}",
disks=[
types.AttachedDisk(
device_name="foo",
boot=True,
auto_delete=True,
initialize_params=types.AttachedDiskInitializeParams(
disk_size_gb=10,
disk_type=f"zones/{zone}/diskTypes/pd-balanced",
source_image="projects/cos-cloud/global/images/cos-stable-101-17162-40-13",
),
),
],
network_interfaces=[
types.NetworkInterface(
access_configs=[
types.AccessConfig(
name="External NAT",
network_tier="STANDARD",
),
],
),
],
metadata=types.Metadata(
items=[{
"key":"gce-container-declaration",
"value": value,
}],
),
service_accounts=[
types.ServiceAccount(
email=email,
scopes=[
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring.write",
"https://www.googleapis.com/auth/servicecontrol",
"https://www.googleapis.com/auth/service.management.readonly",
"https://www.googleapis.com/auth/trace.append"
],
),
],
),
)
response=client.insert(
request=request,
)

Find Google Cloud Platform Operations Performed by a User

Is there a way to track what Google Cloud Platform operations were performed by a user? We want to audit our costs and track usage accordingly.
Edit: there's a Cloud SDK (gcloud) command:
compute operations list
that lists actions taken on Compute Engine instances. Is there a way to see what user performed these actions?
While you can't see a list of gcloud commands executed, you can see a list of API actions. gcloud beta logging surface help with listing/reading logs, but via the console it's a bit harder to use. Try checking the logs on the cloud console.
If you wish to only track Google Cloud Project (GCP) Compute Engine (GCE) operations with the list command for the operations subgroup, you are able to use the --filter flag to see operations performed by a given user $GCE_USER_NAME:
gcloud compute operations list \
--filter="user=$GCE_USER_NAME" \
--limit=1 \
--sort-by="~endTime"
#=>
NAME TYPE TARGET HTTP_STATUS STATUS TIMESTAMP
$GCP_COMPUTE_OPERATION_NAME start $GCP_COMPUTE_INSTANCE_NAME 200 DONE 1970-01-01T00:00:00.001-00:00
Note: feeding the string "~endTime" into the --sort-by flag puts the most recent GCE operation first.
It might help to retrieve the entire log object in JSON:
gcloud compute operations list \
--filter="user=$GCE_USER_NAME" \
--format=json \
--limit=1 \
--sort-by="~endTime"
#=>
[
{
"endTime": "1970-01-01T00:00:00.001-00:00",
. . .
"user": "$GCP_COMPUTE_USER"
}
]
or YAML:
gcloud compute operations list \
--filter="user=$GCE_USER_NAME" \
--format=yaml \
--limit=1 \
--sort-by="~endTime"
#=>
---
endTime: '1970-01-01T00:00:00.001-00:00'
. . .
user: $GCP_COMPUTE_USER
You are also able to use the Cloud SDK (gcloud) to explore all audit logs, not just audit logs for GCE; it is incredibly clunky, as the other existing answer points out. However, for anyone who wants to use gcloud instead of the console:
gcloud logging read \
'logName : "projects/$GCP_PROJECT_NAME/logs/cloudaudit.googleapis.com"
protoPayload.authenticationInfo.principalEmail="GCE_USER_NAME"
severity>=NOTICE' \
--freshness="1d" \
--limit=1 \
--order="desc" \
--project=$GCP_PROJECT_NAME
#=>
---
insertId: . . .
. . .
protoPayload:
'#type': type.googleapis.com/google.cloud.audit.AuditLog
authenticationInfo:
principalEmail: $GCP_COMPUTE_USER
. . .
. . .
The read command defaults to YAML format, but you can also get your audit logs in JSON:
gcloud logging read \
'logName : "projects/$GCP_PROJECT_NAME/logs/cloudaudit.googleapis.com"
protoPayload.authenticationInfo.principalEmail="GCE_USER_NAME"
severity>=NOTICE' \
--format=json \
--freshness="1d" \
--limit=1 \
--order="desc" \
--project=$GCP_PROJECT_NAME
#=>
[
{
. . .
"protoPayload": {
"#type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": {
"principalEmail": "$GCE_USER_NAME"
},
. . .
},
. . .
}
]