go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/apps/provider-scaffold/template/provider/provider.go.template (about)

     1  package provider
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  
     7  	"go.mondoo.com/cnquery/llx"
     8  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
     9  	"go.mondoo.com/cnquery/providers-sdk/v1/plugin"
    10  	"go.mondoo.com/cnquery/providers-sdk/v1/upstream"
    11  	"{{ .GoPackage }}/connection"
    12  	"{{ .GoPackage }}/resources"
    13  )
    14  
    15  type Service struct {
    16  	runtimes         map[uint32]*plugin.Runtime
    17  	lastConnectionID uint32
    18  }
    19  
    20  func Init() *Service {
    21  	return &Service{
    22  		runtimes:         map[uint32]*plugin.Runtime{},
    23  		lastConnectionID: 0,
    24  	}
    25  }
    26  
    27  func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error) {
    28  	flags := req.Flags
    29  	if flags == nil {
    30  		flags = map[string]*llx.Primitive{}
    31  	}
    32  
    33  	conf := &inventory.Config{
    34  		Type: req.Connector,
    35  		Options: map[string]string{},
    36  	}
    37  
    38  	// Do custom flag parsing here
    39  
    40  	asset := inventory.Asset{
    41  		Connections: []*inventory.Config{conf},
    42  	}
    43  
    44  	return &plugin.ParseCLIRes{Asset: &asset}, nil
    45  }
    46  
    47  func (s *Service) Connect(req *plugin.ConnectReq, callback plugin.ProviderCallback) (*plugin.ConnectRes, error) {
    48  	if req == nil || req.Asset == nil {
    49  		return nil, errors.New("no connection data provided")
    50  	}
    51  
    52  	conn, err := s.connect(req, callback)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	// We only need to run the detection step when we don't have any asset information yet.
    58  	if req.Asset.Platform == nil {
    59  		if err := s.detect(req.Asset, conn); err != nil {
    60  			return nil, err
    61  		}
    62  	}
    63  
    64  	return &plugin.ConnectRes{
    65  		Id:        conn.ID(),
    66  		Name:      conn.Name(),
    67  		Asset:     req.Asset,
    68  		Inventory: nil,
    69  	}, nil
    70  }
    71  
    72  // Shutdown is automatically called when the shell closes.
    73  // It is not necessary to implement this method.
    74  // If you want to do some cleanup, you can do it here.
    75  func (s *Service) Shutdown(req *plugin.ShutdownReq) (*plugin.ShutdownRes, error) {
    76  	return &plugin.ShutdownRes{}, nil
    77  }
    78  
    79  func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallback) (*connection.{{ .CamelcaseProviderID }}Connection, error) {
    80  	if len(req.Asset.Connections) == 0 {
    81  		return nil, errors.New("no connection options for asset")
    82  	}
    83  
    84  	asset := req.Asset
    85  	conf := asset.Connections[0]
    86  	var conn *connection.{{ .CamelcaseProviderID }}Connection
    87  	var err error
    88  
    89  	switch conf.Type {
    90  	default:
    91  		s.lastConnectionID++
    92  		conn, err = connection.New{{ .CamelcaseProviderID }}Connection(s.lastConnectionID, asset, conf)
    93  	}
    94  
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	var upstream *upstream.UpstreamClient
   100  	if req.Upstream != nil && !req.Upstream.Incognito {
   101  		upstream, err = req.Upstream.InitClient()
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  	}
   106  
   107  	asset.Connections[0].Id = conn.ID()
   108  	s.runtimes[conn.ID()] = &plugin.Runtime{
   109  		Connection:     conn,
   110  		Callback:       callback,
   111  		HasRecording:   req.HasRecording,
   112  		CreateResource: resources.CreateResource,
   113  		Upstream:       upstream,
   114  	}
   115  
   116  	return conn, err
   117  }
   118  
   119  func (s *Service) detect(asset *inventory.Asset, conn *connection.{{ .CamelcaseProviderID }}Connection) error {
   120    // TODO: adjust asset detection
   121    asset.Id = conn.Conf.Type
   122  	asset.Name = conn.Conf.Host
   123  
   124  	asset.Platform = &inventory.Platform{
   125  		Name:   "{{ .ProviderID }}",
   126  		Family: []string{"{{ .ProviderID }}"},
   127  		Kind:   "api",
   128  		Title:  "{{ .ProviderName }}",
   129  	}
   130  
   131  	// TODO: Add platform IDs
   132  	asset.PlatformIds = []string{"//platformid.api.mondoo.app/runtime/oci/"}
   133  	return nil
   134  }
   135  
   136  func (s *Service) GetData(req *plugin.DataReq) (*plugin.DataRes, error) {
   137  	runtime, ok := s.runtimes[req.Connection]
   138  	if !ok {
   139  		return nil, errors.New("connection " + strconv.FormatUint(uint64(req.Connection), 10) + " not found")
   140  	}
   141  
   142  	args := plugin.PrimitiveArgsToRawDataArgs(req.Args, runtime)
   143  
   144  	if req.ResourceId == "" && req.Field == "" {
   145  		res, err := resources.NewResource(runtime, req.Resource, args)
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  
   150  		rd := llx.ResourceData(res, res.MqlName()).Result()
   151  		return &plugin.DataRes{
   152  			Data: rd.Data,
   153  		}, nil
   154  	}
   155  
   156  	resource, ok := runtime.Resources.Get(req.Resource + "\x00" + req.ResourceId)
   157  	if !ok {
   158  		// Note: Since resources are internally always created, there are only very
   159  		// few cases where we arrive here:
   160  		// 1. The caller is wrong. Possibly a mixup with IDs
   161  		// 2. The resource was loaded from a recording, but the field is not
   162  		// in the recording. Thus the resource was never created inside the
   163  		// plugin. We will attempt to create the resource and see if the field
   164  		// can be computed.
   165  		if !runtime.HasRecording {
   166  			return nil, errors.New("resource '" + req.Resource + "' (id: " + req.ResourceId + ") doesn't exist")
   167  		}
   168  
   169  		args, err := runtime.ResourceFromRecording(req.Resource, req.ResourceId)
   170  		if err != nil {
   171  			return nil, errors.New("attempted to load resource '" + req.Resource + "' (id: " + req.ResourceId + ") from recording failed: " + err.Error())
   172  		}
   173  
   174  		resource, err = resources.CreateResource(runtime, req.Resource, args)
   175  		if err != nil {
   176  			return nil, errors.New("attempted to create resource '" + req.Resource + "' (id: " + req.ResourceId + ") from recording failed: " + err.Error())
   177  		}
   178  	}
   179  
   180  	return resources.GetData(resource, req.Field, args), nil
   181  }
   182  
   183  func (s *Service) StoreData(req *plugin.StoreReq) (*plugin.StoreRes, error) {
   184  	return nil, errors.New("not yet implemented")
   185  }
   186  
   187  func (s *Service) MockConnect(req *plugin.ConnectReq, callback plugin.ProviderCallback) (*plugin.ConnectRes, error) {
   188  	return nil, errors.New("mock connect not yet implemented")
   189  }